# C++11的MyMuduo网络库 **Repository Path**: li-gouhi2333/c11-my-muduo ## Basic Information - **Project Name**: C++11的MyMuduo网络库 - **Description**: 参考陈硕大神Muduo网络库编写的网络库。使用C+11新特性。 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-02-22 - **Last Updated**: 2023-07-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 项目简介 ======== 使用C++11 模仿 muduo 库实现non-blocking + IO-multiplexing + loop线程模型的高并发 TCP 服务器模型,改进了原本Muduo库中未使用C++11的多线程、线程通信部分。 非阻塞既是说通过回调来处理通知事件 多路I/O复用是指能够同时监听多个Socket 高并发多线程能够提高资源利用性能 使用方法 ======== 运行autobuild.sh自动编译脚本,该脚本会将动态库以及头文件添加进系统目录。在你的工程中包含。 Reactor模型结构 =============== ![cd2df05ad169f38fce6f22c301d57c08.jpg](media/1302509d6eb08e5a2c88f204312dbcaa.jpg) Muduo模型组件 ============= ![851c6354963b0e6f97b5a4f498788bb6.jpg](media/a4c83fc5ba97123c31493a8e8291a18c.jpg) ![muduo.png](media/980d331de7f8be4ed80713c6e5d25ca8.png) 组件介绍 ======== Logger ------ logger组件顾名思义,就是对于日志信息的封装,主要作用就是给用户提供一些提示信息,如,登陆成功等;还有就是当代码量上去了之后,不可避免的会出现一些编码的错误,此时日志信息就可以给我提供一些错误信息,方便我们进行调试。其主要包含以下模块: LOG\_INFO:提示信息。 LOG\_ERROR:错误信息(不影响程序正常执行)。 LOG\_FATAL:致命错误信息(导致程序崩溃)。 LOG\_DUBUG:调试信息。 Channel ------- channel可以理解为通道,对应到reactor模型中即为Event。它封装了用于网络通信的sockfd、sockfd所对应的用户感兴趣的事件events,以及存储发生事件的revents。每一个Channel对应一个TcpConnection,在新连接到来,TcpConnection建立时由TcpConnection通知EventLoop调用Poller方法将Channel注册到Poller上。 主要实现了以下功能: - 更新epoll中用户所关心的事件。 - 采用bind绑定器和functional函数对象机制提供了设置用户关心事件发生后的回调处理函数。 - 当有用户感兴趣的事件发生时,根据发生事件的不同,调用用户事先设定的回调函数,处理事件(Poller通知EventLoop,EventLoop通知Channel,Poller和Channel不直接通信)。 也就是说,channel其实就是将Event以及Handler打包后的产物,用sockfd进行标识,同时提供了一些函数接口,便于后续操作。 EPollPoller:继承Poller抽象类 ------------------------------ **Poller**组件是多路事件分发器的核心,在muduo库中,poller组件其实就是对poll和epoll的一个抽象,在实现上采用多态机制,为poll和epoll提供的抽象类,poll和epoll通过继承poller并重写poller所提供的纯虚函数接口,以实现多路转接模型。它封装了sockfd以及指向sockfd所打包的channel的指针,以sockfd为键,以channel\*为值存储到无序关联容器unordered\_map(存储了这个Poller中的所有的channel)中。并且给派生类即EpollPoller提供了可供其重写的纯虚函数: - 开启事件监听。 - 更新epoll中用户所关心事件。 - 接收EventLoop删除、更新channel的操作。  // EventLoop的方法 =》 Poller的方法     void updateChannel(Channel \*channel);     void removeChannel(Channel \*channel);     bool hasChannel(Channel \*channel); // Poller给所有IO复用具体实现保留统一的接口     virtual Timestamp poll(int timeoutMs, ChannelList \*activeChannels) = 0;     virtual void updateChannel(Channel \*channel) = 0;     virtual void removeChannel(Channel \*channel) = 0; **EpollPoller**组件通过继承Poller并且重写其提供的纯虚函数接口,实现epoll多路转接模型。EpollPoller其实就是将epoll的接口封装成了一个类,提供向epoll中添加、修改、删除所关心的事件的接口以及开启事件监听的函数接口,并且对外还提供了向用户返回发生事件的接口以及更新channel的接口。Poller和EpollPoller对应的reactor模型中即就是Demultiplex事件分发器。 EventLoop --------- EventLoop组件即为事件循环,对应的reactor模型中即就是Reactor反应堆,注意,它自身带有一个WakeupChannel已经注册到了Poller上,用于唤醒自身。主要的功能就是: - 开启事件循环。(调用Poller中的poll) - 对EpollPoller以及channel进行操作,穿插在EpollPoller和channel之间,channel调用EventLoop提供的函数接口将用户所感兴趣的事件注册到epoll上,还可以通过EventLoop对用户所感兴趣的事件在epoll上进行更新、删除等操作,当有事件发生时,EpollPoller就会将所发生的事件通过参数反馈给EventLoop,EventLoop再调用用户预先在channel中设置的回调函数EventHandler对发生事件进行处理。 - 如果当前所发生的事件(此时需要其他loop执行回调函数,而此时该loop阻塞在epoll\_wait监听事件。通过QueueInLoop将该回调加入到对应Loop的PendingFunctors\_中,并通过向wakeupfd中写入数据唤醒该loop),就需要唤醒阻塞epoll\_wait上的线程,执行相应的EventHandler操作。通过系统调用接口eventfd创建一个wakeupFd,并打包成channel注册到所在线程的EpollPoller中,如果调用回调的loop所属线程不是当前线程,那么就向这个loop所属的wakeupFd对应的事件中写入数据,用以唤醒loop所在的线程执行相应的回调。 // 把cb放入队列中,唤醒loop所在的线程,执行cb void EventLoop::queueInLoop(Functor cb) {     {         std::unique\_lock\ lock(mutex\_);         pendingFunctors\_.emplace\_back(cb);//emplace\_back是一种更高效的push\_back操作     }     // 唤醒相应的,需要执行上面回调操作的loop的线程了     // \|\| callingPendingFunctors\_的意思是:当前loop正在执行回调,但是loop又有了新的回调     if (!isInLoopThread() \|\| callingPendingFunctors\_)     {         wakeup(); // 唤醒loop所在线程     } } Thread & EventLoopThread & EventLoopThreadPool ---------------------------------------------- ### Thread Thread组件顾名思义就是线程,所以其实现的功能主要也都是与线程相关的: 1. 创建线程并且执行线程入口函数。 2. 设置线程等待。 ### EventLoopThread组件 EventLoopThread事件循环线程,是对Thread组件的一层封装,主要功能就是提供启动底层的线程(调用Thread所提供的创建线程的方法)并且为将要创建的线程设置入口函数。 ### EventLoopThreadPool组件 EventLoopThreadPool组件为事件循环线程池,主要功能是: 1. 设置线程数量threadnum。 2. 创建用户所设置的threadnum个线程,并且将每个线程上所创建的loop的地址返回。 3. 通过轮询算法选择一个loop,将新连接用户所关心的channel分发给loop。 Socket & Acceptor组件 --------------------- ### Socket组件 Socket组件是对TCP网络编程的一个抽象,将TCP网络编程的函数接口抽象成了Socket类,主要的功能就是: 1. 创建套接字。 2. 绑定地址信息。 3. 监听新连接的到来。 4. 获取新连接用于网络通信的套接字socket。 ### Acceptor组件 Acceptor组件是在Socket组件的基础上进行了封装,主要功能: 1. 创建用于监听和获取新连接的accpetSocket,为其绑定地址信息。 2. 将accpetSocket以及其感兴趣的事件(新连接到来)打包成accpetChannel。 3. 开启监听新连接到来并将accpetChannel通过EventLoop注册到所属的epoll中去。 4. 其实acceptorChaneel所属的loop就是mainLoop。 buffer组件 ---------- buffer组件是网络库底层的数据缓冲区,主要解决的问题就是当TCP接收缓冲区较小但是用户需发送数据过多或者TCP发送数据过多但是我们只需要读取其中一部分数据,未发送的数据或者未读取的数据的存储问题。它主要实现的功能就是: 1. 将未发送的数据或者未读取的数据存储下来。 2. 对缓冲区进行扩容。 3. 向调用发返回读、写缓冲区的大小。 ### buffer模型: ![截图.png](media/0442df8c6a23e50781e7007227ee5e65.png) TcpConnection组件 ----------------- TcpConnection组件主要做于新用户连接后的一系列回调操作。一个连接成功的客户端对应一个TcpConnection,其主要封装了连接成功的用户的socket,Channel,当有读写事件发生时,对发送缓冲区和接收缓冲区进行操作,并且为当前连接的用户设置各种的回调操作。 1. 通过C++11从boost库中引入的bind绑定器和functional函数对象机制,为当前连接所对应的channel设置各种事件的回调函数。 2. 实现EventHandler事件发生。 3. 发送数据。 TcpServer组件 ------------- TcpServer组件是所有组件的总调度者,供用户创建TcpServer对象,使用TCP服务器的,它对外提供了设置建立新连接回调函数、读写消息回调函数、开启事件循环、设置线程数量并创建线程等函数接口,对内提供并绑定了有新用户连接时的回调函数,通过轮询算法分发新连接用户到EventLoopThread上(即subLoop)。并且设置了关闭连接的回调函数。它主要封装了Acceptor以及EventLoopThreadPool。 项目各模块交互流程梳理 ====================== 用户使用muduo库编写服务器程序的流程 ----------------------------------- 编写TcpServer类:在构造函数中绑定连接建立和断开的回调函数以及可读写事件的回调函数,设置线程个数(一般和CPU核心数相同),对外start()提供启动服务的接口,实现连接建立和断开的回调函数以及可读写事件的回调函数。 编程main函数:创建事件循环对象,提供InetAddress地址信息,创建TcpServer服务器对象,启动服务,开启事件循环。 TCP服务器建立 ------------- 用户建立TCP服务器,在创建TcpServer对象时,其构造函数会创建Acceptor对象以及EventLoopThreadPool对象,并且通过Acceptor的setNewConnectionCallback方法为Acceptor对象中所封装的acceptChannel绑定有新连接到来时的回调函数。 其中Acceptor对象的构造函数会做以下三件事: 1. 创建用于监听新用户到来的监听套接字listenfd,绑定地址信息。 2. 将listenfd和监听新用户连接事件打包成acceptChannel。 3. 设置事件发生后的回调函数。 当用户调用start()方法启动服务后,EventLoopThreadPool对象就会调用自己的start方法创建用户所设置的threadnum个线程,并且启动所创建的线程。Acceptor对象调用自己的listen方法做以下两件事: 1. 调用Sockets所封装的listen方法开始监听新连接的到来; 2. 调用acceptChannel的enableReading方法,将listenfd所关心的事件注册到EpollPoller上。 用户调用所创建的事件循环对象的loop方法,开启事件循环。在loop方法中,会通过EventLoop所封装的poller对象调用EpollPoller的poll方法,即调用epoll\_wait开始监听注册的事件。此时服务器的服务就启动完成了。 新用户的连接 ------------ 当有新用户连接时,当前loop对象底层的EpollPoller就会监听到acceptChannel的读事件发生,进而调用构造Acceptor对象时绑定的回调函数,在回调函数中,会做以下两件事: 1. 调用Acceptor所封装的acceptSocket\_的accept函数获取新连接用于通信的套接字sockfd以及地址信息。 2. 调用构造TcpServer时绑定的处理新连接的回调函数,并将新连接用于通信的套接字以及地址信息传给该回调函数。 处理新连接的回调函数所做事情: 1. 根据轮询算法选择一个subLoop。 2. 获取sockfd对应的地址信息。 3. 创建TcpConnection对象,把选择的subLoop绑定到TcpConnection所属线程上,将用户设置的连接建立和断开的回调函数以及可读写事件等回调函数通过TcpConnection对象所提供的方法设置给当前TcpConnection连接,并且设置服务器内部关闭连接的回调函数。 4. 执行TcpConnection对象建立连接的回调函数(唤醒TcpConnection所属线程,建立连接回调函数)。在建立连接回调函数中,向EpollPoller中注册当前sockfd所打包channel的读写事件,并且执行用户设置的连接建立和断开的回调函数。此时,连接就建立成功了。 ![截图.png](media/3900052065869f4c3c8d91cb7e5282ba.png) 数据通信 -------- 当用户有读事件发生时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的读事件的回调函数进行处理。而这个回调函数是用户通过TcpServer的setMessageCallback方法设置的,TcpServer在处理新连接的回调函数中构造TcpConnection对象时,通过TcpConnection所提供的setReadCallback将其绑定到TcpConnection对象所对应的channel中的。所以最终相应到了用户所设置的可读写事件的回调函数上。 关闭连接 -------- 当连接断开时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的关闭连接事件的回调函数进行处理。TcpConnection所做的事情就是删除该连接以及将所对应的sockfd以及channel从Poller的无序管理容器中删除,并且将注册在EpollPoller上的事件全部删除。 ![muduo模型框架.png](media/04e5a8a8f3e49b2681a3dea08236f1f6.png)