Muduo库
非阻塞IO+IO复用
Event Loop: 一个不断监听事件的发生,并调用相应回调函数的机制。处理异步I/O操作的编程模式,通过一个无限循环监听和分发事件。
注:在Event Loop中,应用首先注册感兴趣的事件处理器(如网络连接、文件I/O等),然后进入一个循环,等待这些事件的发生,并在事件发生时调用相应的处理器来响应事件。
Non-Blocking 几乎总是和 I/O 多路复用一起使用:
- 单用Non-Blocking需要轮询检测I/O状态,会浪费CPU资源。
- I/O多路复用不能与阻塞I/O共存:I/O 多路复用判断某个 socket 可能处于某种状态(如“可读”或“可写”),但这并不保证后续的阻塞 I/O 操作一定能够成功完成。
- 对于“可读”状态,可能只是表示内核缓冲区中有一些数据,但实际的数据量可能不足以满足
read()
请求(比如请求读取 100 字节,但缓冲区只有 50 字节)。 - 对于“可写”状态,可能只是表示内核缓冲区有空间,但如果你尝试写入大量数据,可能会超出缓冲区容量,导致
write()
阻塞。
- 对于“可读”状态,可能只是表示内核缓冲区中有一些数据,但实际的数据量可能不足以满足
注:阻塞 I/O 会导致线程被挂起,从而无法处理其他 socket 上的事件。
Reactor模型
Reactor模型:是一个异步I/O模型,它不会阻塞程序的执行等待某个I/O操作完成,而是通过事件驱动的方式来处理I/O操作。Reactor设计模式是用于处理服务请求的事件处理模式,由一个或多个输入同时传递给服务处理程序,服务处理程序然后对传入的请求进行解复用,并将它们同步分派给关联的请求处理程序。
Reactor模式的核心概念:事件分发和处理分离
- 事件驱动:在Reactor模型中,所有输入(如客户端连接请求、数据到达等)都被视为事件。这些事件由Reactor负责监听和分发。
- 解复用:当多个输入同时到达服务端时,Reactor会将这些输入进行解复用(demultiplexing),即根据事件类型将它们分类。例如,连接请求和数据接收是两种不同的事件类型,会被分配给不同的处理器处理。
- 同步分派:一旦事件被解复用后,Reactor会同步地将这些事件分派给对应的事件处理器(EventHandler)。这里的“同步”意味着Reactor以同步方式分派事件。
一个Reactor模型的网络服务器交互流程:
- 注册感兴趣的事件和回调函数: 将感兴趣的事件及预置的回调函数Handler注册到Reactor反应堆上
启动Reactor反应堆: 一旦所有的事件都注册完毕,Reactor 就开始监听这些事件的发生。
启动事件分发器:持续等待直到有事件发生,一旦事件分发器检测到某个事件已经准备好,就会通知 Reactor。
- 处理事件并响应: Reactor会通过map表找到Event事件对应的事件处理器来读取用户请求
Main Reactor 和 Sub Reactors:为了提高系统的可扩展性(连接和读写请求相分离)和性能(负载均衡)
当有新的客户端连接请求到达时,Main Reactor 调用
accept()
接受连接,并获取客户端的 socket 文件描述符,将其分配给某个 Sub Reactor。注:这里一般是Main Reactor 直接调用
accept()
,而不用事件处理器。因为接受新连接的逻辑简单且高效Sub Reactors 的任务是监听已经建立的连接(即客户端的 socket),并处理(调用对应的事件处理器)这些连接上的 I/O 事件,比如数据到达、可写等。
ET模式与LT模式
LT模式: 当 epoll_wait
检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用 epoll_wait
时,还会再次向应用程序通告此事件,直到该事件被处理。
ET模式:当epoll_wait
检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait
调用将不再向应用程序通知这一事件。所以 ET 模式在很大程度上降低了同一个epoll
事件被重复触发的次数,因此一半了来说效率比 LT 模式高。
实际上muduo库采用的为LT模式,主要好处如下:
- 不会丢失数据或者消息: 应用没有读取完数据,内核是会不断上报的。
- 低延迟处理: 每次读数据只需要一次系统调用;照顾了多个连接的公平性,不会因为某个连接上的数据量过大而影响其他连接处理消息。