TcpServer

61 阅读6分钟

一、TcpServer分析

首先TcpServer对象创建一个Acceptor对象,Acceptor创建listenfd,并将listenfd封装成acceptChannel,先设置acceptChannel的读事件(acceptChannel.enableReading()),然后通过loop把acceptChannel注册到当前EventLoop的Poller(Poller封装poll/epoll),Poller就监听acceptChannel上的事件。一旦acceptChannel上有事件发生,acceptChannel就会执行相应的读事件回调函数(因为之前对读事件感兴趣),即Acceptor::handleRead(),handleRead就通过accept函数返回一个和客户端通信的connfd,然后执行相应的回调,这个回调就是TcpServer预先注册的setNewConnectionCallback函数

image.png 这个回调函数首先将新用户的地址保存在peerAddr,并调用accept函数获取和客户端通信的connfd,然后传入Acceptor::newConnetionCallback_并执行,而这个回调就是TcpServer对象的成员acceptor_提前调用Acceptor::setNewConnectionCallback设置的

image.png 新用户到来就会执行TcpServer提前注册的回调函数,这个回调函数就会将与客户通信的connfd封装成Channel(实际上mainLoop封装成了TcpConnection,包含一个成员变量Channel),采用轮询算法选择一个subloop,通过其数据成员wakeupFd_写8字节数据来唤醒,然后分发Channel

image.png 运行在主线程的mainLoop调用TcpServer注册好的回调函数TcpServer::newConnection,选择一个ioLoop,但是每个线程的事需要等到CPU调度到这个线程的时候才能处理,所以EventLoop提供了两个方法runInLoop和queueInLoop

image.png

  • runInLoop:如果当前执行线程就是创建EventLoop对象的线程,就直接执行回调cb
  • queueInLoop:当前执行的线程并不是创建EventLoop对象的线程,先把回调cb放到队列,然后往该EventLoop对象的wakeupFd_上写数据通知对应线程

为什么我们总要使用EventLoop::isInLoopThread判断执行线程的是否就是创建EventLoop对象的线程呢?

因为muduo网络库并没有在mainLoop和subLoop之间添加同步队列,没有使用mainLoop生产Channel和subLoop消费Channel的生产者——消费者模型,而是采用了one loop per thread的方法,让每个线程监听自己的wakeupFd_,执行mainLoop的线程往wakeupFd_上写数据就是通知subLoop,需要处理新的Channel

image.png

mainLoop传递给sunLoop的TcpConnection包含两个成员:

  • connectionCallback_:就是我们使用muduo编程时,给服务器注册用户连接的创建和断开回调,当有新连接到来或者断开时subLoop就会调用
  • messageCallback_:使用muduo编程时,给服务器注册的用户读写事件回调,TcpConnection里的Channel有读写事件时会被调用

二、TcpServer.h

#pragma once

/**
 * 用户使用muduo库编写服务器程序
 */
#include "EventLoop.h"
#include "Acceptor.h"
#include "InetAddress.h"
#include "noncopyable.h"
#include "EventLoopThreadPool.h"
#include "Callbacks.h"

#include <functional>
#include <memory>
#include <atomic>
#include <unordered_map>

//对外的服务器编程使用的类
class TcpServer : noncopyable
{
public:
    using ThreadInitCallback = std::function<void(EventLoop*)>;

    // 预置两个是否重用port的选项
    enum Option
    {
        kNoReusePort,
        kReusePort,
    };

    TcpServer(EventLoop* loop,
                const InetAddress &listenAddr,
                const std::string& nameArg,
                Option option = kNoReusePort);
    ~TcpServer();

    void setThreadInitcallback(const ThreadInitCallback &cb) { threadInitCallback_ = cb; }
    void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; }
    void setMessageCallback(const MessageCallback &cb) { messageCallback_ = cb; }
    void setWriteCompleteCallback(const WriteCompleteCallback &cb) { writeCompleteCallback_ = cb; }

    //设置底层subloop的个数
    void setThreadNum(int numThreads);

    //开始服务器监听
    void start();
private:
    void newConnection(int sockfd,const InetAddress &peerAddr);
    void removeConnection(const TcpConnectionPtr &conn);
    void removeConnectionInLoop(const TcpConnectionPtr &conn);

    //存储连接的名字和对应的连接
    using ConnectionMap = std::unordered_map<std::string, TcpConnectionPtr>;

    EventLoop* loop_;//baseloop 用户自己定义的loop
    const std::string ipPort_;// 保存服务器的ip port
    const std::string name_;// 保存服务器的name
    std::unique_ptr<Acceptor> acceptor_;// 运行在mainloop的Acceptor,用于监听listenfd,等待新用户连接
    std::shared_ptr<EventLoopThreadPool> threadPool_;//事件循环线程池 one loop per thread

    ConnectionCallback connectionCallback_;//有新连接时的回调
    MessageCallback messageCallback_;//有读写消息时的回调
    WriteCompleteCallback writeCompleteCallback_;//消息发送完成后的回调

    ThreadInitCallback threadInitCallback_;//loop线程初始化的回调
    std::atomic_int started_;

    int nextConnId_;
    ConnectionMap connections_;//保存所有的连接
};

三、TcpServer.cc

  • 建立连接

TcpServer对象构造的时候,就会同时构造Acceptor对象和threadPool_对象,并调用Acceptor::setNewConnectionCallback方法,把TcpServer::newConnection传给成员Acceptor::newConnetionCallback_,Acceptor::newConnetionCallback_又会在新连接到来时在Acceptor::handleRead中调用

即TcpServer::newConnection最终调用处是在Acceptor::handleRead

TcpServer::newConnection主要做:组装新连接的名字、创建TcpConnection连接对象、并设置相应的一系列回调函数

  • 断开连接

断开连接时,Poller会设置Channel的revents并将发生事件的Channel对象放入activateChannels_,EventLoop会遍历activateChannels_,并通过自己的Channel成员调用Channel::handleEvent处理EPOLLHUP事件,handleEvent会调用Channel::closeCallBack_,而这个Channel::closeCallBack_就是TcpConnection::handleClose

#include "TcpServer.h"
#include "Logger.h"
#include "TcpConnection.h"

#include <functional>
#include <strings.h>

//定义成静态的,否则会和TcpConnection.cc中名字冲突
static EventLoop* CheckLoopNotNull(EventLoop* loop)
{
    if(loop == nullptr)
    {
        LOG_FATAL("%s:%s:%d mainLoop is null \n", __FILE__, __FUNCTION__, __LINE__);
    }
    return loop;
}

// 构造函数,用户需要传入一个EventLoop,即mainLoop,还有服务器的ip和监听的端口
TcpServer::TcpServer(EventLoop* loop,
                const InetAddress &listenAddr,
                const std::string& nameArg,
                Option option)
                :loop_(CheckLoopNotNull(loop))//不接受用户给loop传空指针
                ,ipPort_(listenAddr.toIpPort())
                ,name_(nameArg)
                ,acceptor_(new Acceptor(loop_,listenAddr,option = kReusePort))
                ,threadPool_(new EventLoopThreadPool(loop_,name_))
                ,connectionCallback_()
                ,messageCallback_()
                ,nextConnId_(1)
{
    // 有新用户连接时,会调用Acceptor::handleRead,然后handleRead调用TcpServer::newConnection,
    // 使用两个占位符,因为TcpServer::newConnection方法需要新用户的connfd以及新用户的ip port
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection,this,std::placeholders::_1,std::placeholders::_2));
}
TcpServer::~TcpServer()
{
    for (auto &item : connections_)
    {
        //这个局部的shared_ptr智能指针对象,出右括号,可以自动释放new出来的TcpConnection资源
        TcpConnectionPtr conn(item.second);
        item.second.reset();

        //销毁连接
        conn->getLoop()->runInLoop(
            std::bind(&TcpConnection::connectDestroyed, conn)
        );
    }
}

//设置底层subloop的个数
void TcpServer::setThreadNum(int numThreads)
{
    threadPool_->setThreadNum(numThreads);
}

//开始服务器监听
void TcpServer::start()
{
    if(started_++ == 0)//防止一个TcpServer对象被start多次
    {
        threadPool_->start(threadInitCallback_);//启动底层的loop线程池
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); //启动listen监听新用户的连接
    }
}

// 根据获取到connfd来封装TcpConnection对象
// 一条新连接到来,Acceptor根据轮询算法选择一个subloop并唤醒,把和客户端通信的connfd封装成Channel分发给subloop
// TcpServer要把newConnection设置给Acceptor,让Acceptor对象去调用,工作在mainLoop
// connfd是用于和客户端通信的fd,peerAddr封装了客户端的ip port
void TcpServer::newConnection(int sockfd,const InetAddress &peerAddr)
{
    //轮询算法,选择一个subLoop来管理channel
    EventLoop* ioLoop = threadPool_->getNextLoop();
    char buf[64] = {0};
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;
    //newConnection名称
    std::string connName = name_ + buf;

    LOG_INFO("TcpServer::newConnection [%s] - new connection [%s] from %s \n",
        name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());
    
    //用通信的sockfd来获取其绑定的本机的ip地址和port
    sockaddr_in local;
    ::bzero(&local, sizeof local);
    socklen_t addrlen = sizeof local;
    // getsockname通过connfd拿到本地的sockaddr地址信息写入local
    if(::getsockname(sockfd, (sockaddr*)&local, &addrlen) < 0)
    {
        LOG_ERROR("sockets::getLocalAddr");
    }
    InetAddress localAddr(local);

    //根据连接成功的sockfd,创建TcpConnection连接对象
    // 将connnfd封装成TcpConnection,TcpConnection有一个Channel的成员变量,这里就相当于把一个TcpConnection对象放入了一个subloop
    TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));

    connections_[connName] = conn;
    // 下面的回调都是用户设置给TcpServer的,然后TcpServer => TcpConnection => Channel,Channel会把自己封装的fd和events注册到Poller,发生事件时Poller调用Channel的handleEvent方法处理
    // 就比如这个messageCallback_,用户把on_message(messageCallback_)传给TcpServer,TcpServer会调用TcpConnection::setMessageCallback,那么TcpConnection的成员messageCallback_就保存了on_message
    // TcpConnection会把handleRead设置到Channel的readCallBack_,而handleRead就包括了TcpConnection::messageCallback_(on_message)
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);

    //设置如何关闭连接的回调
    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)
    );
    //直接调用TcpConnection::connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));

}

//连接已断开,从connectionMap里移除
void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
    loop_->runInLoop(
        std::bind(&TcpServer::removeConnectionInLoop, this, conn)
    );
}

//从事件循环中删除连接
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{
    LOG_INFO("TcpServer::removeConnectionInLoop [%s] - connection %s \n", name_.c_str(), conn->name().c_str());
    connections_.erase(conn->name());
    EventLoop* ioLoop = conn->getLoop();
    ioLoop->queueInLoop(
        std::bind(&TcpConnection::connectDestroyed, conn)
    );
}