IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> muduo网络库执行流程 -> 正文阅读

[C++知识库]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和Channel,socket绑定本地地址,给Acceptor注册读事件回调(Acceptor只需要处理读事件即可)。此外TcpServer::start()时,Acceptor才开始监听,往mainLoop的Poller上注册listenfd

2. 构造EventLoopThreadPool对象

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

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

TcpServer设置Acceptor在有新连接到来时,需要执行的回调newConnetionCallback_,有新连接到来时需要执行TcpServer::newConnection。实际上,有新连接到来时,Acceptor对象会先执行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

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

  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都有的成员上写数据,让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出作用域后,会自动释放对象空间

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:51:17  更:2022-05-08 07:52:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 23:37:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码