项目总结:muduo网络库的整体流程

Unix/Linux上的五种IO模型

项目总结:muduo网络库的整体流程-qc的编程笔记
项目总结:muduo网络库的整体流程-qc的编程笔记
项目总结:muduo网络库的整体流程-qc的编程笔记

好的网络服务器设计

在这个多核时代,服务端网络编程如何选择线程模型呢? 赞同libev作者的观点:one loop per
thread is usually a good model,这样多线程服务端编程的问题就转换为如何设计一个高效且易于使
用的event loop,然后每个线程run一个event loop就行了(当然线程间的同步、互斥少不了,还有其
它的耗时事件需要起另外的线程来做)。


event loop 是 non-blocking 网络编程的核心,在现实生活中,non-blocking 几乎总是和 IO
multiplexing 一起使用,原因有两点:
1.没有人真的会用轮询 (busy-pooling) 来检查某个 non-blocking IO 操作是否完成,这样太浪费
CPU资源了。
2.IO-multiplex 一般不能和 blocking IO 用在一起,因为 blocking IO 中
read()/write()/accept()/connect() 都有可能阻塞当前线程,这样线程就没办法处理其他 socket
上的 IO 事件了。


所以,当我们提到 non-blocking 的时候,实际上指的是 non-blocking + IO-multiplexing,单用其
中任何一个都没有办法很好的实现功能

muduo核心组件示意图

项目总结:muduo网络库的整体流程-qc的编程笔记

muduo大致流程

1. TcpServer 的初始化

  • TcpServer 是整个服务器的核心组件,负责处理客户端连接请求。
  • 初始化 TcpServer 对象时,会创建一个 Acceptor,它包含一个监听文件描述符(listenfd)和一个 acceptChannel,用于监听新连接事件。
  • 通过 setNewConnectionCallbackTcpServer::newConnection 方法设置为 Acceptor 的回调函数,当有新连接到来时,触发此回调。

2. 主线程的 EventLoop (mainLoop)

  • mainLoop 是主线程中的 EventLoop 对象,负责监听 Acceptor 的事件。
  • TcpServer::start 方法会启动 EventLoopThreadPool,创建多个子线程,每个子线程运行一个独立的 EventLoop,称为 subLoop
  • 主线程的 mainLoop 主要负责监听新的连接请求,而客户端的具体数据处理会分配给 subLoop

3. EventLoopThreadPool 的工作机制

  • EventLoopThreadPool 是一个线程池,用于管理多个 EventLoop 线程。
  • 通过 start 方法启动 EventLoopThreadPool,创建 numThreads_ 个线程,每个线程都运行一个 subLoop
  • 每个 subLoop 都是一个独立的 EventLoop,和主线程的 mainLoop 平行工作,用于处理客户端的 I/O 事件。

4. Acceptor 的事件处理

  • Acceptor 的作用是接受新的客户端连接。
  • 它在主线程的 mainLoop 中监听 listenfd 的读事件,一旦有新的连接请求到来,就会触发 acceptChannel 的读回调,即 handleRead() 方法。
  • handleRead() 方法会调用 newConnectionCallback,即 TcpServer::newConnection,来处理新的连接。

5. TcpServer::newConnection:处理新连接

  • newConnection 中,TcpServer 创建一个新的 TcpConnection 对象来表示客户端连接。
  • 然后,TcpServer 使用轮询算法选择一个 subLoop(从多个子线程的 EventLoop 中选择一个),将新的连接分配给该 subLoop
  • 具体步骤:
    • 根据轮询算法选择 subLoop,将新的 connfd 转换为一个 channel
    • TcpConnection 的回调函数注册到 subLoop 上,以便在该子线程中处理客户端的事件。

6. Reactor 模式的实现(每个线程一个 Reactor

  • 每个 EventLoop 充当一个 Reactor,负责在其线程中管理和处理所有事件。
  • 主线程的 mainLoop 只负责新的连接请求的监听,而所有的客户端 I/O 事件都会在 subLoop 中处理。
  • 每个 subLoop 都包含一个 EPollPoller,用于监听事件,通过 poll() 方法轮询事件并执行相应的回调。

7. 线程间的唤醒机制

  • 当主线程将新的连接分配给某个 subLoop 时,需要唤醒该子线程,以便立即处理新的连接。
  • 通过 eventfdsocketpair 实现线程间的唤醒机制。
  • 每个 subLoop 线程都有一个 wakeUpFd,主线程通过写入 wakeUpFd 唤醒子线程。

8. TcpConnection 的回调处理

  • TcpConnection 表示一个客户端连接,负责具体的读写数据操作。
  • 它包含了两个主要的回调函数:
    • onConnection:当连接建立时触发,业务逻辑可以选择在这里处理一些连接初始化工作。
    • onMessage:当有数据到达时触发,用于读取和处理客户端发送的数据。
  • 这些回调会被注册到 subLoop 中,在 subLoop 监听到客户端事件后,自动执行相应的回调函数。

9. 数据发送和接收

  • 当客户端发送数据时,onMessage 回调被触发,TcpConnection 会从 connfd 中读取数据并进行处理。
  • 当服务器需要发送数据给客户端时,TcpConnection 负责将数据写入 connfd,并确保数据被正确发送给客户端。

10.定时器

---->eventloop中timerqueue对象创建完成,此时timerqueue中构造函数会建立一个timerqueue::m_timerfd和timerqueue::m_timerfdChannel,用于注册可读定时器事件和唤醒poller::poll()。

---->eventloop对象创建完成,

---->eventloop::runAfter()  这个是为用户提供操作的,用来在eventloop中设置一个定时操作。

--->eventloop::runAt()

---->timerqueue::addTimer() 到此我们可以看出来,eventloop::run_XXX系列函数归根结底就是在timerqueue中添加一个定时器并设置定时器回调函数,那么timerqueue是如何知道现在有定时器到时了呢?

---->eventloop::runInLoop(timerqueue::addTimerInLoop)  

---->eventloop::queueInLoop(timerqueue::addTimerInLoop),此时任务队列不为空,eventloop::m_wakefd会立即唤醒poller::poll()执行任务队列中的函数(timerqueue::addTimerInLoop)

----->resetTimerfd(timerqueue::m_timerfd,expiration),到现在才只是完成了timerfd_settime()操作,经过expiration秒后,timerqueue::m_timerfd就会发起可读定时器事件,

---->poller::poll()被m_timerfd可读事件唤醒,通过timerqueue::m_timerChannel来处理这个可读定时器事件

------>timerqueue::m_timerChannel会回调timerqueue::handleRead()函数:1.找到所有过期的定时器集合,

2.运行这些过期定时器的回调函数。

至此,从eventloop::runAfter()到真正的定时器函数调用才最终完成。

项目总结:muduo网络库的整体流程-qc的编程笔记

11.连接的关闭与资源清理

  • 当连接需要关闭时,TcpConnection 会调用 closeCallback
  • closeCallback 通常指向 TcpServer::removeConnection 方法,用于将连接从 subLoop 中移除,并执行清理工作。
  • 关闭操作完成后,TcpConnection 对象会被销毁,释放连接资源。

总结

  1. 主线程通过 mainLoop 负责监听新连接请求。
  2. 新连接到来时,TcpServer 将其分配给一个 subLoop,由子线程负责处理。
  3. EventLoopThreadPool 提供了多个 subLoop 线程,实现多线程处理能力。
  4. 每个 subLoop 都是一个 Reactor,负责在各自的线程中管理客户端的 I/O 事件。
  5. 通过 eventfdsocketpair 实现线程间的唤醒,使得主线程可以及时通知子线程处理新连接。
  6. TcpConnection 用于管理每个客户端连接,包括数据的读写和连接的关闭。

这样的设计实现了一个高效的多线程网络服务器结构,充分利用了多核 CPU 的并行处理能力,实现了高性能的网络 I/O 处理。