muduo网络库执行流程

用户使用muduo时编写的代码如下:

#include <mymuduo/TcpServer.h>

#include <string>
#include <functional>

class EchoServer{
    public:
        EchoServer(EventLoop* loop, const InetAddress& addr, const std::string &name)
            : server_(loop, addr, name)
            , loop_(loop)
        {
            // 注册回调函数
            server_.setConnectionCallback(std::bind(&EchoServer::on_connection, this, std::placeholders::_1));
            server_.setMessageCallback(std::bind(&EchoServer::on_message, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            
            // 设置合适的subloop线程
            server_.setThreadNum(3);
        }

        void start(){
            server_.start();
        }

    private:
        void on_connection(const TcpConnectionPtr& conn){
            if(conn->connected()){
                LOG_INFO("conn up : %s \n", conn->peerAddress().toIpPort().c_str());
            }else{
                LOG_INFO("conn down : %s \n", conn->peerAddress().toIpPort().c_str());
            }
        }

        void on_message(const TcpConnectionPtr& conn, Buffer* buff, Timestamp time){
            std::string msg = buff->retrieveAllAsString();
            conn->send(msg);
            conn->shutdown();  // 关闭写端,触发EPOLLHUP,Channel调用closeCallback_
        }

        EventLoop* loop_;
        TcpServer server_;
};

int main(){
    EventLoop loop;
    InetAddress addr(8888, "127.0.0.1");
    EchoServer server(&loop, addr, "EchoServer-01");  // Acceptor non-blocking listenfd
    server.start();   // 会将listenfd封装成acceptChannel,添加到mainloop
    loop.loop();     // 启动EpollPoller

    return 0;
}

首先构造一个EventLoop,创建封装了本地地址和端口号的InetAddress对象,然后就构造一个EchoServer对象,底层还是直接构造的TcpServer对象,然后启动TcpServer以及事件循环EventLoop

一、TcpServer对象构造时,都做了哪些事

在这里插入图片描述

1. 构造一个Acceptor对象

首先构造了一个Acceptor对象,工作在mainLoop

在这里插入图片描述

Acceptor的构造函数首先创建非阻塞的socket,socket绑定本地地址,给Acceptor注册读事件回调(Acceptor只需要处理读事件即可)。此外TcpServer::start()时,Acceptor才开始监听,往mainLoop的Poller上注册listenfd

2. 构造EventLoopThreadPool对象

构造EventLoopThreadPool对象,此时还没开启loop线程

3. 设置有新连接到来时执行的回调

TcpServer设置Acceptor在有新连接到来时,需要执行的回调newConnetionCallback_,有新连接到来时需要执行TcpServer::newConnection。实际上,有新连接到来时,Acceptor对象会执行readCallBack_,readCallBack_就是Acceptor::handleRead,Acceptor::handleRead内部再调用newConnetionCallback_,即TcpServer::newConnection

在这里插入图片描述

二、TcpServer.start()做了什么

在这里插入图片描述
首先通过atomic_int变量控制TcpServer重复start,然后启动底层线程池、执行Acceptor的listen方法

1. 启动底层线程池

threadPool_->start(threadInitCallback_);

在这里插入图片描述
线程池调用start方法后,根据numThreads_创建执行subloop的线程,然后调用EventLoopThread::startLoop

startLoop方法除了调用Thread对象的start方法,还会返回thread的数据成员EventLoopThread::loop_
在这里插入图片描述
Thread::start方法会调用我们在EventLoopThread构造函数中设置的EventLoopThread::threadFunc方法
在这里插入图片描述
EventLoopThread::threadFunc方法创建了EventLoop对象loop,loop.loop()然后启动事件循环,这就是启动了subloop

构造EventLoop的时候,会注册wakeupFd到Poller
在这里插入图片描述
那EventLoop是什么时候构造的呢?

EventLoopThreadPool::start被调用时,会构造EventLoopThread对象,然而此时该事件循环线程对象EventLoop为空,并没有构造EventLoop对象,EventLoop对象是在 调用thread->startLoop()然后调用thread_.start() 时调用EventLoopThread::threadFunc方法时构造的
在这里插入图片描述

2. 执行Acceptor的listen方法

loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));

在这里插入图片描述
这一行代码就相当于开启fd的listen模式,并把listenfd注册在Poller上,即使用epoll监听

至此,subloop全都启动了,listenfd也注册到Poller上了,已经全部准备好了

三、用户调用loop.loop

用户调用loop.loop,就是启动mainloop,就是开始执行IO复用方法监听fd是否有事件发生,然后调用Channel对应的回调函数处理

四、整个服务器启动的流程

  1. 构造Acceptor对象,创建listenfd,给Acceptor注册回调
  2. 构造TcpServer对象,同时包含了注册回调,设置底层线程的个数,构造EventLoopThreadPool对象,设置有新连接到来时执行的回调
  3. TcpServer.start开启subloop,构造EventLoop对象的同时将wakeupfd注册到Poller,能够让主线程mainloop来唤醒子线程loop
  4. Acceptor.listen,把listenfd注册到Poller上。
  5. 最后启动mainLoop

五、新连接到来时,服务器处理

新连接到来时,注册在Poller上的listenfd会调用readCallBack_
在这里插入图片描述
Acceptor封装的设置的readCallBack_就是Acceptor::handleRead
在这里插入图片描述
Acceptor::handleRead中又调用了newConnetionCallback_
在这里插入图片描述
这个newConnetionCallback_就是TcpServer给设置的TcpServer::newConnection
在这里插入图片描述
即有新连接到来时,会调用TcpServer::newConnection,函数如下:
在这里插入图片描述
首先构造了一个TcpConnection对象,创建对象的同时也就将该对象分发给了一个subloop,即TcpConnection的数据成员loop_指向这个subloop

然后将TcpConnection对象放入TcpServer的数据成员connections_进行存储,然后设置了一系列的回调函数connectionCallback_、messageCallback_、writeCompleteCallback_、CloseCallback,这些回调函数都是TcpServer给TcpConnection设置的,而这些回调函数会在TcpConnection的以下函数中调用:
在这里插入图片描述
而上述TcpConnection的成员函数早就被当作回调函数注册在了Channel上
在这里插入图片描述
发生事件时,Channel会调用相应的回调函数,即调用到了TcpConnection的成员函数,而在TcpConnection的成员函数内部又调用了TcpServer给TcpConnection设置的回调函数,这些TcpServer的回调数据成员又都是用户编程时,调用set方法给TcpServer设置的

TcpServer提供用于设置回调函数的接口:
在这里插入图片描述
用户调用方式:
在这里插入图片描述
设置完一系列回调后,还有最后一行代码:

ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));

如果设置过subloop的数量,这个ioLoop所属的线程一定不是执行TcpServer::newConnection的线程,因为新连接的创建一定是在mainloop中完成,所以这里的runInLoop一定会执行queueInLoop
在这里插入图片描述
在queueInLoop里面,这个执行mainloop的线程先把当前待执行的回调函数TcpConnection::connectEstablished放入subloop的数据成员pendingFunctors_

callingPendingFunctors_ == true表示subloop的线程正在执行pendingFunctors_里的回调函数,不需要唤醒,直接走人
callingPendingFunctors_ == false则表示subloop暂时没有客户的读写事件,正在epoll_wait阻塞等待,执行mainloop的线程则会执行wakeup方法,往每个EventLoop都有的成员wakeupFd上写数据,让epoll_wait返回发生的事件,这个subloop所在的线程就会执行pendingFunctors_里的回调了

反之如果没有设置过subloop的数量,整个Reactor模型只有一个mainloop,那直接执行回调函数TcpConnection::connectEstablished
在这里插入图片描述
connectEstablished方法设置TcpConnection的状态为kConnected、将Channel对象和一条TcpConnection绑定、往Poller上注册Channel的读事件,调用用户传入的on_connection方法

那为什么Channel和TcpConnection需要绑定呢?
在这里插入图片描述
因为EventLoop通过Poller调用epoll_wait返回后,会通过Channel调用相应的回调处理事件,而Channel的回调函数都是TcpConnection设置的
在这里插入图片描述
如果由于某些原因TcpConnection对象不存在了,一旦发生事件,Channel再执行这些回调也就没有意义了

所以Channel用一个数据成员weak_ptr来观察TcpConnection对象,每次Channel执行回调函数前都会用weak_ptr的lock方法检查TcpConnection对象是否存活,存活则调用回调函数,否则不调用

六、有消息到来时,服务器处理

有已连接用户的消息到来时,EventLoop通过Poller调用epoll_wait返回,然后调用Channel的readCallBack_
在这里插入图片描述
TcpServer给Channel设置readCallBack_过程如下:

首先Channel的readCallBack_是通过Channel的成员方法setReadCallBack设置的,设置Channel的回调肯定是在TcpConnection中进行的
在这里插入图片描述
可以看到,TcpConnection给Channel的成员readCallBack_设置的是TcpConnection::handleRead,这个函数里面调用了messageCallback_
在这里插入图片描述
messageCallback_也是通过调用TcpConnection::setMessageCallback设置的,给TcpConnection设置方法,那只能是在TcpServer中进行

TcpServer调用setMessageCallback那就是用户调用的了,如下:

server_.setMessageCallback(std::bind(&EchoServer::on_message, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

所以,这样一层一层设置,Channel调用readCallBack_,就是调用TcpConnection::handleRead,TcpConnection::handleRead又会调用messageCallback_,即调用用户通过TcpServer设置的自定义函数on_message

七、连接断开时,服务器处理

连接断开包括:客户端断开和服务端主动断开

连接断开后,EventLoop通过Poller调用epoll_wait返回,然后调用Channel的closeCallBack_
在这里插入图片描述
显然,Channel的回调函数Channel::closeCallBack_是TcpConnection设置的,是TcpConnection::handleClose
在这里插入图片描述

在这里插入图片描述
在TcpConnection::handleClose中,首先设置TcpConnection的状态为kDisconnected,然后把Channel对应的fd以及fd感兴趣的事件从epoll内核事件表中删除

然后执行用户传入的on_connection方法,再执行TcpConnection::closeCallback_方法,这个方法是新连接到来时,在TcpServer::newConnection设置的,即TcpServer::removeConnection方法
在这里插入图片描述
在这里插入图片描述
最终调用到TcpServer::removeConnectionInLoop方法,先在TcpServer用于记录连接的connections_中删除当前已经断开连接的TcpConnection对象,然后获取该TcpConnection对象所属subloop,然后让对应线程执行TcpConnection::connectDestroyed

在这里插入图片描述
一般来说,if语句是不会满足条件的,我们在TcpConnection::handleClose中,就设置了TcpConnection对象的状态为kDisconnected,最后调用Channel::remove方法,Channel::remove会调用到EPollPoller::removeChannel

先从Poller的容器中移除断开连接的Channel,接下来这个if语句一般是不会成立的
在这里插入图片描述
因为index在前面的TcpConnection::handleClose中会调用:

channel_->disableAll();  // 对任何事件都不感兴趣,从epoll红黑树中删除

这一句就会把index设置为kDeleted

最后就是把channel的状态设置为kNew

TcpConnection::handleClose中,用shared_ptr指向当前断开连接的TcpConnection对象,这个shared_ptr出作用域后,会自动释放对象空间

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值