I/O模型

阻塞I/O    客户端通过connect向服务器发起连接时,connect将首先发送同步报文给服务器,等待服务器返回确认报文段。如果服务器的确认报文段没有立即到达客户端,则connect调用将被挂起,直到客户端收到确认报文段并唤醒connect调用。

非阻塞I/O  无论事件是否发生,都立即返回 -1 并设置 errno、如何区分出错和正常返回?对acceptsendrecv,事件未发生时,errno被设置为EAGAIN(”再来一次“)或者EWOULDBLOCK(”期望阻塞“);connect而言,errno则被设置为EINPROGRESS(”在处理中“)。

非阻塞I/O 通常要与其他I/O通知机制一起使用,比如I/O复用和SIGIO信号

1I/O复用

应用程序向内核注册一组事件,内核通过I/O复用函数把其中就绪的事件通知给应用程序。Linux常用的I/0复用函数是selectpollepoll_wait。值得一提的是!I/O复用本身是阻塞的,但由于能同时监听多个I/O事件,因此可以提高程序效率。

2SIGIO信号

为一个文件描述符指定宿主进程,该文件描述符上有事件发生时,SIGIO信号的信号处理函数将被触发,在信号处理函数里对目标文件描述符执行非阻塞I/O操作。由于是信号触发,没有阻塞阶段。

同步I/O VS 异步I/O

1、同步I/O要求用户代码自行执行I/O操作,异步I/O则由内核执行I/O操作(用户缓冲区与内核缓冲区之间数据的移动是由用户还是内核执行的区别)。

2、同步I/O向用户程序通知的是I/O就绪事件,异步I/O向用户程序通知的是I/O完成事件。

4 两种高效的事件处理模式

服务器程序通常需要处理三类事件:I/O事件、信号及定时事件。

4.1 Reactor模式

有主线程和工作线程。主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。工作线程负责读写数据、接受新的连接、处理客户请求。

使用同步I/O模型(以epoll_wait为例)实现的Reactor模式的工作流程是:

1、主线程往epoll内核事件表中注册socket上的读就绪事件。

2、主线程调用epoll_wait等待数据可读写。

3、有数据可读写时,epoll_wait通知主线程。主线程将socket可读写事件放入请求队列

4、睡眠在请求队列上的某个工作线程被唤醒,如果是读事件,则从socket读取数据并处理客户请求;如果是写事件,则往socket上写入服务器处理客户请求的结果。

 

4.2 Proactor模式

Reactor模式不同,Proactor将所有I/O操作交给主线程和内核来处理。工作线程仅仅负责业务逻辑。

使用异步I/O模型(以aio_readaio_write为例)实现的Proactor模式的工作流程是:

1、调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读(写)缓存区的位置,以及读操作完成时如何通知应用程序(这里以信号为例)。

2、主线程继续处理其他逻辑。

3、当socket上的数据被读入用户缓存区(或者用户缓存区的数据被写入socket)之后,内核向应用程序发送一个信号,以通知应用程序数据可用(或者通知应用程序数据已经发送完毕)。

4、应用程序预先定义好的信号处理函数选择一个工作线程做善后处理,比如决定是否关闭socket

4.3 模拟Proactor模式

模拟Proactor模式是用同步I/O模拟Proactor模式。与Reactor的主要区别在第三步第四步:

3、有数据可读写时,epoll_wait通知主线程。如果是读事件,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。如果是写事件,主线程往socket上写入服务器处理客户端请求的结果。

以上是模拟Proactor模式的最后一步,I/O操作由主线程来完成。

5 两种高效的并发模式

程序可以分为计算密集型和I/O密集型。由于I/O操作远远没有CPU的计算速度快,如果程序阻塞于I/O操作将耗费大量的CPU时间,因此需要并发编程。如果被I/O阻塞的执行线程主动放弃CPU(或者由操作系统进行调度),CPU就可以用来做更有意义的事情,显著提高利用率。

并发编程主要有多进程和多线程两种方式。

5.1 半同步/半异步模式

这里的同步指的是程序完全按照代码序列的顺序执行。异步指的是程序的执行需要由系统事件来驱动。

前面I/O模型讲的同步异步区分的是内核向应用程序通知的是何种I/O事件(就绪事件还是完成事件),以及谁来完成I/O读写(应用程序还是内核)。

同步线程:按照同步方式运行的线程。

异步线程:按照异步方式运行的线程。

1、一般来说,同步线程就是工作线程,异步线程是主进程。

2、异步线程用于监听客户请求,将客户请求封装成请求对象并插入请求队列。请求队列通知某个工作在同步模式的工作线程来读取并处理该请求。

以下是半同步/半异步模式的几种变体:

在高效的半同步/半异步模式中,每个线程都维持自己的事件循环,各自独立地监听不同的事件,并且都工作在异步模式,因此并非严格的半同步/半异步模式。