muduo源码分析——TcpServer和Acceptor

这篇文章用于分析muduo的TcpServer类和Acceptor类,原本打算将TcpConnection也放到这里一起聊的,但是那个太多啦,一篇文章太长会让人读的很不舒服把。

当然我用的代码是其他大神开发的flamingo的源码,这是一个基于muduo开发的即时聊天服务器
具体的地址https://blog.csdn.net/analogous_love/article/details/69481542

不废话了,进入正题吧

TcpServer 成员及构造

我有一个习惯,查看一个新class的时候,首先看private中有哪些成员,可能这样更好捋清class之间的关系把。

	typedef std::map<string, TcpConnectionPtr> ConnectionMap;
    private:
		EventLoop*                                      loop_;  // the acceptor loop
		const string                                    hostport_;
		const string                                    name_;
		std::shared_ptr<Acceptor>                       acceptor_; // avoid revealing Acceptor
        std::shared_ptr<EventLoopThreadPool>            eventLoopThreadPool_;
		ConnectionCallback                              connectionCallback_;
		MessageCallback                                 messageCallback_;
		WriteCompleteCallback                           writeCompleteCallback_;
		ThreadInitCallback                              threadInitCallback_;
		std::atomic<int>                                started_;
		int                                             nextConnId_;  // always in loop thread
		ConnectionMap                                   connections_;//每个生成的链接的指针都会保存在这里
	};

一个TcpServer有一个EventLoop,先简单的理解为用来接收新的连接。
(英文备注基本上是陈硕大佬自己写的备注)。

关于类之间的关系,可以看到持有一个Acceptor和一个EventLoopThreadPool的指针。
再看看TcpServer初始化的代码。

TcpServer::TcpServer(EventLoop* loop,
    const InetAddress& listenAddr,
    const std::string& nameArg,
    Option option)
    : loop_(loop),
    hostport_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    //threadPool_(new EventLoopThreadPool(loop, name_)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    started_(0),
    nextConnId_(1)
{
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));
}

TcpServer实例被创建的时候,会new一个Acceptor,并由自己对应得指针指向这个Acceptor,我们也可以从最后看出,Acceptor设置了回调函数,绑定为了TcpServer的newConnection函数,也就是说,当这一个Acceptor的newConnectionCallback_函数被调用的时候,会回调到TcpServer的newConnection函数来。

TcpServer是提供给客户代码的接口。那么构造是需要客户代码来协助实现的。

值得注意的是Option是一个枚举类型

		enum Option
		{
			kNoReusePort,
			kReusePort,
		};

这里提到了端口复用的问题,端口复用可以让多个套接字绑定同一个端口,往往在于服务器主动断开连接或者产生异常的时候,因为会有一个time_wait=2MSL,具体可以设置。所以端口还会被占用。为了保证端口可用,就要使用端口复用,详细就不展开聊了

start 函数

用户代码需要通过start函数来启动TcpServer,至少我是用“启动”这个词的,不知道这个词会不会有点偏差。

void TcpServer::start(int workerThreadCount/* = 4*/)
{
    if (started_ == 0)
    {
        eventLoopThreadPool_.reset(new EventLoopThreadPool());//初始化事件循环线程池
        eventLoopThreadPool_->Init(loop_, workerThreadCount);//acceptor loop
        eventLoopThreadPool_->start();
        
        //threadPool_->start(threadInitCallback_);
        //assert(!acceptor_->listenning());
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
        started_ = 1;
    }
}

首先,关于started_这个应该好理解吧,就是有没有启动。
注意到EventLoopThreadPool在start中才被new和init,但是Acceptor在TcpServer构造的时候就被new了,我不是很清楚为什么会分开,我个人的理解,可能是创建和初始化EventLoopThreadPool,意味着要开几个新线程吧,所以留到确定要启动Tcpserver才执行吧。

值得注意的是 loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
我们来看看runInLoop这个函数

void EventLoop::runInLoop(const Functor& cb)
{
	if (isInLoopThread())//如果是本线程,则直接运行,否则加入队列,等待运行
	{
		cb();
	}
	else
	{
		queueInLoop(cb);
	}
}

总体来说是执行里面的std::bind(&Acceptor::listen, acceptor_.get())
至于EventLoop后面会开一篇文章专门来分析。简单来说这里最终执行了listen函数,开始监听这个socket。

stop函数

void TcpServer::stop()
{
    if (started_ == 0)
        return;

    for (ConnectionMap::iterator it = connections_.begin(); it != connections_.end(); ++it)
    {
        TcpConnectionPtr conn = it->second;
        it->second.reset();
        conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
        conn.reset();
    }

    eventLoopThreadPool_->stop();

    started_ = 0;
}

关闭TcpServer的基本操作就是:
迭代器,遍历ConnectionMap(存放所有的TcpConnection),这里首先reset了map中的TcpConnectionPtr,我不是很理解为什么要,创建一个再reset掉之前的,为什么不用it->second->getLoop()->runInLoop(),是不是会出现内存泄露的问题,不是很理解,有待进一步查证!!!
getLoop()函数是获取当前TcpConnection对应的EventLoop,其实是在创建TcpConnection的时候,会从线程池中选一个已经存在的EventLoop给它。应该说这个TcpConnection属于这个EventLoop。

小插曲

整个框架是事件驱动的,什么意思,很多实际需要执行的函数,都会被送入EventLoop中等待执行。所以你会在很多地方看到runInLoop函数,这个函数的作用是如果当前函数是属于当前这个EventLoop线程的,就立即执行,否则push到pendingFunctors_中,等待执行。具体我想到了EventLoop的时候再好好梳理一下。

当TcpConnection被销毁完后,再停掉线程池,将标志started_设为0,就算完成了stop任务了

newConnection函数

这个应该算是比较重要的一个函数了,这个函数负责开始建立连接。

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
    loop_->assertInLoopThread();
    EventLoop* ioLoop = eventLoopThreadPool_->getNextLoop();
    char buf[32];
    snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);
    ++nextConnId_;
    string connName = name_ + buf;

    LOGD("TcpServer::newConnection [%s] - new connection [%s] from %s", name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());

    InetAddress localAddr(sockets::getLocalAddr(sockfd));//生成一个InetAddress,有一个新的sockaddr
    // FIXME poll with zero timeout to double confirm the new connection
    // FIXME use make_shared if necessary
    TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
    connections_[connName] = conn;
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)); // FIXME: unsafe
    //该线程分离完io事件后,立即调用TcpConnection::connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

我们来看看第二行EventLoop* ioLoop = eventLoopThreadPool_->getNextLoop();
这一行其实是创建一个指针,用来指向当前线程池中下一个线程的函数,(假设线程池有6个线程,那么每个连接就会被平均分配到这6个,算是负载均衡?)

接下来就是生成了一个新的InetAddress(封装了sockaddr),然后创建了新的TcpConnection,并放入map中,为TcpConnection设置回调函数,最后调用了TcpConnection::connectEstablished,由TcpConnection做收尾的工作。

Acceptor

接下来来分析一下Acceptor这个类,顾名思义Acceptor接收者,其实就是接收连接用的,我们看看它的成员变量。

    private:
        EventLoop*            loop_;
        Socket                acceptSocket_;
        Channel               acceptChannel_;//接受通道
        NewConnectionCallback newConnectionCallback_;
        bool                  listenning_;

在看看它的初始化

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
    : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie()),//返回一个int型,fd表示socket,socket 被设置成了非阻塞
    acceptChannel_(loop, acceptSocket_.fd()),//设置通道,对应的循环和fd
    listenning_(false)   
{
#ifndef WIN32
    idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
#endif

    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(reuseport);
    acceptSocket_.bindAddress(listenAddr);
    acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));//注册了回调函数,
}

主要来说就是一个通道channel和一个socket。在之前整体的分析中,我也简单分析过读事件建立连接的基本过程:epoll->Channel->Acceptor->TcpServer->TcpConnection。
成员acceptSocket_其实就是侦听socket,会被调用listen方法和accept方法。
Channel是通道,每一个TcpConnection和TcpServer所持有的Acceptor都有一个Channel连接到外部,所以都会持有一个Channel。顾名思义“通道”,通往外界的道路。

对于Acceptor比较重要的函数就是handleRead了

void Acceptor::handleRead()//要准备新建一个链接
{
    loop_->assertInLoopThread();
    InetAddress peerAddr;//相当于一个socket,被封装了的sockaddr_in
    //FIXME loop until no more
    int connfd = acceptSocket_.accept(&peerAddr);//执行socket::accept接收连接请求,并将新创建的socket的信息写入peerAddr,然后返回对应的文件描述符。
    if (connfd >= 0)
    {
         string hostport = peerAddr.toIpPort();
         LOGD("Accepts of %s", hostport.c_str());
        //newConnectionCallback_实际指向TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
        if (newConnectionCallback_)
        {
            newConnectionCallback_(connfd, peerAddr);
        }
        else
        {
            sockets::close(connfd);
        }
    }
    else
    {
        LOGSYSE("in Acceptor::handleRead");

#ifndef WIN32
        if (errno == EMFILE)
        {
            ::close(idleFd_);
            idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
            ::close(idleFd_);
            idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
        }
#endif
    }
}

这个函数是用来处理对应的Channel调用回调函数时,所执行的内容。

看看这个函数int connfd = acceptSocket_.accept(&peerAddr);//返回创建的新的套接字
这个函数的作用是,用Acceptor对应的socket,去accept了新的连接请求,并将创建的新的文件描述符socket(linux下socket被当作文件)等信息,写入了peerAddr中,InetAddress是一个封装sockaddr_in的类,也可以认为它就是一个sockaddr_in,这样理解起来会比较轻松。

接着是调用newConnectionCallback_,让TcpServer去创建TcpConnections。前面多了一个判断,目的是为了加一层保障,防止TcpServer没有绑定好Callback函数。

后记

我在想一个问题,或许我把每个功能单独写一篇文章会比按类来写更加好吧。现在写感觉很多调用到其他类的函数都没有展开来说明。以后写blog注意一下吧,muduo就算啦,接着这样写下去吧!

你可能感兴趣的:(c++网络编程)