Muduo是由陈硕大佬个人开发的C++网络库,最近在剖析其源码,在此做一些归纳整理。
框架模型
Muduo网络库的框架模型主要基于Reactor模式,这是一种用于处理多个I/O事件的高效并发模型。
Reactor模式
Reactor模式是一种事件驱动的处理模式,它用于同步地派发基于事件或状态的请求到一个或多个服务处理程序。在Muduo中,Reactor模式被用来处理网络I/O事件,如数据的可读、可写以及错误事件。
核心组件
- EventLoop:EventLoop是Muduo网络库的核心组件,它负责事件循环的驱动。EventLoop内部维护了一个事件分发器(通常是基于epoll或poll的系统调用),用于监听文件描述符上的事件。当事件发生时,EventLoop会调用相应的事件处理函数。
- Channel:Channel类封装了一个文件描述符Socket和与之相关的事件处理逻辑。每个Channel对象都注册到EventLoop中,当文件描述符上有事件发生时,EventLoop会通知对应的Channel对象进行处理。
- Acceptor:Acceptor类负责监听指定的端口,并接受来自客户端的连接请求。当有新的连接请求到达时,Acceptor会创建一个新的TcpConnection对象,并将其与客户端进行关联。同时,Acceptor会将新创建的TcpConnection对象添加到EventLoop的管理列表中。
- TcpConnection:TcpConnection类代表了一个TCP连接。它封装了与客户端之间的通信逻辑,包括数据的读写、连接的保持和关闭等操作。TcpConnection对象通常是由Acceptor调用TcpServer的回调函数来创建并添加到EventLoop的管理列表中的。
工作流程
- 初始化:程序启动时,创建EventLoop对象,并设置监听地址和端口。同时,创建Acceptor对象并将其注册到EventLoop(mainLoop)中。
- 监听端口:Acceptor开始监听指定的端口,等待客户端的连接请求。
- 接受连接:当有新的连接请求到达时,Acceptor会调用TcpServer的回调函数创建一个新的TcpConnection对象,并将其添加到EventLoop(subLoop)的管理列表中。同时,会设置TcpConnection的相关回调函数(如数据到达时的回调函数)。
- 事件循环:EventLoop(subLoop)进入事件循环状态,开始监听文件描述符上的事件。当事件发生时(如TcpConnection上有数据可读),subLoop会调用相应的事件处理函数进行处理。
- 数据读写:在事件处理函数中,可以根据事件的类型进行相应的处理。例如,当TcpConnection上有数据可读时,可以从输入缓冲区中读取数据并进行处理;当需要向客户端发送数据时,可以将数据写入输出缓冲区并等待发送机会。
- 连接管理:TcpConnection对象负责管理与客户端之间的连接状态。当客户端断开连接时,TcpConnection会从EventLoop的管理列表中移除,并释放相关资源。
各模块简介
Channel模块
内含向Poller中注册的文件描述符fd,封装了感兴趣的事件events、Poller返回的发生的事件revents,和一组能够根据fd发生的事件revents进行回调的回调函数callbacks
共有两种Channel,一种是listenfd - acceptorChannel,一种是connfd - connectionChannel
Poller和EPollPoller - Demultiplex
std::unordered_map<int, Channel*> channels_; // key为fd
通过epollctl把Channel中包含的fd注册到epoll上,当epoll返回时,可以通过fd找到Channel,进而进行相应的回调。
EventLoop - Reactor
std::vector<Channel *> activeChannels_;
std::unique_ptr<Poller> poller_;
一个Eventloop管理一堆Channel和一个poller。Channel想注册到poller上或者在poller上修改自己感兴趣的事件,Channel都是通过EventLoop来获取到poller,进而完成相应任务;同时,poller监听到sockfd有相应事件发生,也要通过EventLoop来调用相应Channel的fd的所发生事件的回调函数。
int wakeupFd_; // 一个wakeupfd隶属于一个loop
std::unique_ptr<Channel> wakeupChannel_;
loop执行的时候,驱动底层的事件分发器Demultiplx也即epoll_wait,若没有事件发送,则loop一定阻塞在epoll_wait上;若是想唤醒某个loop的阻塞状态,那就可以通过loop对象获取其对应的wakeupfd,并往wakeupfd写一点数据,来使loop从epoll_wait上返回,这是因为每一个wakeupfd也封装成了一个wakeupChannel,注册在了底层的epoll上。
std::vector<Functor> pendingFunctors_;
如果当前线程要调用其他线程中的loop进行回调操作,则将回调函数存放到pendingFunctors_中,并weakup相应的loop。
Thread、EventLoopThread和EventLoopThreadPool
EventLoop *getNextLoop();
通过轮询算法获取下一个subLoop,如果没有setThreadNum设置多线程,那么获取到的永远是baseLoop;如果有设置过多线程,那么threadPool会驱动底层创建新线程,一个thread对应一个loop,即 one loop per thread
Socket、Acceptor
主要封装了listenfd相关操作,socket创建、bind、listen,listen开启后打包成acceptorChannel,放到baseLoop去执行
Buffer
缓冲区 应用写数据 =》缓冲区 =》 Tcp发送缓冲区 =》 send
+-------------------+------------------+------------------+
| prependable bytes | readable bytes | writable bytes |
| 缓冲区头 | (CONTENT) | 可写空间 |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= size
TcpConnection
一个连接成功的客户端对应一个TcpConnection,封装了Socket、Channel和各种回调函数,还有发送和接收缓冲区
TcpServer
封装Acceptor,EventLoopThreadPool,以及
std::unordered_map<std::string, TcpConnectionPtr> connections_;