五种I/O模型和select函数简介

同步和异步的区别在于是不是要求处理消息者自己来完成将数据从内核缓冲区复制回进程缓冲区的过程

消息者阻塞和非阻塞应该是发生在消息的处理的时刻。阻塞其实就是等待,发出通知,等待结果完成。非阻塞属于发出通知,立即返回结果,没有等待过程

阻塞I/O

connect

调用recv系统调用,如果没有数据则阻塞等待,当数据到来则将数据从内核空间(套接口缓冲区)拷贝到用户空间(recv函数提供的buf),然后recv返回,进行数据处理。

非阻塞I/O

connect
fcntl(fd, F_SETFL, flag | O_NONBLOCK);将套接字标志变成非阻塞。

调用recv,如果设备暂时没有数据可读就返回-1,同时置errno为EWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里,事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次。调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了,在实际应用中非阻塞I/O模型经常与IO multiplexing一起使用。

I/O复用

connect

用select来管理多个I/O,当没有数据时select阻塞,如果在超时时间内数据到来则select返回,再调用recv进行数据的复制,recv返回后处理数据。

信号驱动I/O

connect

先注册SIGIO信号的处理函数,进程继续执行其他操作,当数据到来时会发送SIGIO信号给进程,然后可以在信号处理函数中调用recv进行数据的复制,然后recv返回进行数据处理。

异步I/O

connect

aio_read 函数也会提供一个buf,系统调用进入内核,如果没有数据则立即返回,进程继续执行其他操作,所以叫异步I/O,当数据到来时内核自动复制数据,然后推送给用户空间,通过在aio_read中指定的信号通知进程,让其处理数据

异步I/O跟信号驱动I/O的不同之处在于,它不用调用recv进行数据的复制,如果将后者比做”拉pull“,则前者可以认为是”push推“,push的效率会高点,但aio_read的实现或多或少存在问题,用得也比较少。实践中用得比较多的如boost库的asio也是异步IO

select函数简介

用select管理多个I/O,select阻塞等待,一旦其中的一个或多个I/O检测到我们所感兴趣的事件,select函数返回,返回值为检测到的事件个数,并且返回哪些I/O发送了事件,遍历这些事件,进而处理事件。

当select阻塞返回后,此时调用accept接收连接是不会阻塞的,直接返回已连接套接字,可以认为是select 提前阻塞了。但此时调用write还是可能阻塞的,因为需要写入的空间大小可能缓冲区还不满足。

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数1:读写异常集合中的文件描述符的最大值加1;
参数2:读集合,关心可读事件;
参数3:写集合,关心可写事件;
参数4:异常集合,关心异常事件;
参数5:超时时间结构体

文件描述符的监视范围与第一个参数有关,select函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在fd_set变量中的文件描述符数。但每次新建文件描述符时,其值都会增1,故需将最大的文件描述符值加1再传递到select函数即可。

对于参数2,3,4来说,如果不关心对应事件则设置为NULL即可。注意5个参数都是输入输出参数,即select返回时可能对其进行了修改。时间结构体的传出参数是剩余的时间,如果设置为NULL表示永不超时。

可读事件:

  1. 套接口缓冲区有数据可读
  2. 对等连接的写一半关闭。即接收到FIN段,读操作将返回0
  3. 如果是监听套接口,已完成连接队列不为空时
  4. 套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

可写事件:

  1. 套接口发送缓冲区有空间容纳数据。(连接一旦建立就可写)
  2. 对等连接的读一半关闭。即收到RST段之后,再次调用write操作。
  3. 套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。

异常事件:
套接口存在带外数据(TCP头部 URG标志,16位紧急指针字段)

下面是4个可以对集合进行操作的宏:

void FD_CLR(int fd, fd_set *set); // 清除出集合
int  FD_ISSET(int fd, fd_set *set); // 判断是否在集合中
void FD_SET(int fd, fd_set *set); // 添加进集合中
void FD_ZERO(fd_set *set); // 将集合清零