介绍

一个服务器如何处理多个客户端连接(read->process->write, RPW),显然是用多线程技术。有一个客户端,对应一个线程。一个线程一般会在read上面卡住(因为线程不知道什么时候有数据需要read),这样一旦同一时间客户端变的越来越多,那么线程不能及时释放掉,就会变得越来越多。创建线程和释放线程又会消耗很多资源,那么线程池就很好的解决这个问题。

解决上述办法途径就是,找到有read需要的连接,而不是一个线程只守着一个连接了。最简单的就是,线程去轮询所有连接,看看哪个连接有数据需要读取。但是引入的另一个问题就是,轮询也要消耗系统资源啊。

对此,解决办法是利用I/O多路复用技术(如select, poll, epoll),告诉线程什么时候有数据,有数据了线程才会去read。线程只需要进行系统调用即可,如果没有需要read的数据,那么就阻塞在系统调用,如果有read的数据,系统会唤醒线程。

一个进程往往会监听多个连接,当这些连接有数据需要读取,那么I/O多路复用技术会回调这个进程。

Reactor 模式

对于I/O多路复用技术的封装,就是Reactor模式,直译成反应堆模式。

Reactor模式包括Reactor和任务执行者:

  • Reactor负责监听和分发事件(类似于老板)

    监听系统调用,并根据事件类型分发给资源池,比如连接事件,读写事件等

  • 任务执行者负责处理事件(类似于工人)

    处理客户端事件,即read->process->write

Reactor可以有单个或者多个,任务执行者也会有单个或者多个进程/线程。因此有以下四种模式:

  1. 单Reactor - 单进程/线程:该线程需要负责监听和分发,然后负责RPW,之后继续监听。
  2. 单Reactor - 多进程/线程:主线程需要负责监听和分发,还需要负责读取和发送,工人只需要干活即可。
  3. 多Reactor - 单进程/线程:多个老板指挥一个工人干活,显然没有什么意义
  4. 多Reactor - 多进程/线程:主线程负责监听和分发以及连接的建立,子线程又相当于一个“单Reactor - 单进程/线程”,但是不连接建立

许多项目其实都是采用第四种,又简单又高效,第1的实现其实不如第4简单。

Proactor

Proactor是一种异步网络模式,Reactor是一个非阻塞同步网络模式。

在此之前,先搞懂几个概念:阻塞/非阻塞,同步/异步I/O。

  1. 阻塞同步IO
应用进程   |  操作系统
--------------------------
read     ->  数据未准备
                |
                |
             数据准备数据
             拷贝给应用进程
                |
                |
处理      <-   拷贝完成
  1. 非阻塞同步IO
应用进程   |  操作系统
--------------------------
read     ->  数据未准备
         <- 
可以选择干其他事

read     ->  数据未准备
         <-       
可以选择干其他事

read     ->  数据未准备
         <-       
可以选择干其他事

read     ->  数据未准备
                |
             数据准备数据
             拷贝给应用进程
                |
                |
处理      <-   拷贝完成
  1. 异步IO
应用进程   |  操作系统
--------------------------
read     ->  数据未准备
         <-       
可以选择干其他事
                |
             数据准备数据
             拷贝给应用进程
                |
                |
处理      <-   拷贝完成并通知
  • Reactor:由应用进程自己调用read()函数进行读取,监听了待读取事件
  • Proactor:应用进程提供输入缓冲区,系统将数据拷贝至缓冲区,然后再提醒应用进程,监听了读取完成事件