介绍
一个服务器如何处理多个客户端连接(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可以有单个或者多个,任务执行者也会有单个或者多个进程/线程。因此有以下四种模式:
- 单Reactor - 单进程/线程:该线程需要负责监听和分发,然后负责RPW,之后继续监听。
- 单Reactor - 多进程/线程:主线程需要负责监听和分发,还需要负责读取和发送,工人只需要干活即可。
- 多Reactor - 单进程/线程:多个老板指挥一个工人干活,显然没有什么意义
- 多Reactor - 多进程/线程:主线程负责监听和分发以及连接的建立,子线程又相当于一个“单Reactor - 单进程/线程”,但是不连接建立
许多项目其实都是采用第四种,又简单又高效,第1的实现其实不如第4简单。
Proactor
Proactor是一种异步网络模式,Reactor是一个非阻塞同步网络模式。
在此之前,先搞懂几个概念:阻塞/非阻塞,同步/异步I/O。
- 阻塞同步IO
应用进程 | 操作系统
--------------------------
read -> 数据未准备
|
|
数据准备数据
拷贝给应用进程
|
|
处理 <- 拷贝完成
- 非阻塞同步IO
应用进程 | 操作系统
--------------------------
read -> 数据未准备
<-
可以选择干其他事
read -> 数据未准备
<-
可以选择干其他事
read -> 数据未准备
<-
可以选择干其他事
read -> 数据未准备
|
数据准备数据
拷贝给应用进程
|
|
处理 <- 拷贝完成
- 异步IO
应用进程 | 操作系统
--------------------------
read -> 数据未准备
<-
可以选择干其他事
|
数据准备数据
拷贝给应用进程
|
|
处理 <- 拷贝完成并通知
- Reactor:由应用进程自己调用
read()
函数进行读取,监听了待读取事件 - Proactor:应用进程提供输入缓冲区,系统将数据拷贝至缓冲区,然后再提醒应用进程,监听了读取完成事件