非阻塞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以同步方式分派事件。

image-20250407100736409

一个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 事件,比如数据到达、可写等。

image-20250407101911108

ET模式与LT模式

LT模式:epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用 epoll_wait时,还会再次向应用程序通告此事件,直到该事件被处理。

ET模式:epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。所以 ET 模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此一半了来说效率比 LT 模式高。

实际上muduo库采用的为LT模式,主要好处如下:

  • 不会丢失数据或者消息: 应用没有读取完数据,内核是会不断上报的。
  • 低延迟处理: 每次读数据只需要一次系统调用;照顾了多个连接的公平性,不会因为某个连接上的数据量过大而影响其他连接处理消息。