五种 IO 模型
1. IO 的本质
IO 的本质就是等 + 拷贝。
- 等:等待 IO 条件就绪,比如
recv函数,当输入缓冲区中没有数据时阻塞等待。 - 拷贝:当 IO 条件就绪后,将数据拷贝到内存或外设。
任何 IO 的过程,都包含'等'和'拷贝'这两个步骤,但在实际的应用场景中'等'消耗的时间往往比'拷贝'消耗的时间多,因此要让 IO 变得高效,最核心的办法就是尽量减少'等'的时间。
2. 五种 IO 模型
2.1 阻塞 IO
阻塞 IO 就是在数据准备好之前,系统调用会一直等待。

阻塞 IO 是最常见的 IO 模型,所有的套接字,默认都是阻塞方式。
比如当调用 recv 函数从某个套接字上读取数据时,可能底层数据还没有准备好,此时就需要等待数据就绪,当数据就绪后再将数据从内核拷贝到用户空间,最后 recv 函数才会返回。
以阻塞方式进行 IO 操作的进程或线程,在'等'和'拷贝'期间都不会返回,在用户看来就像是阻塞住了,因此我们称之为阻塞 IO。
2.2 非阻塞 IO
非阻塞 IO 就是,如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回 EWOULDBLOCK 错误码。

非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对 CPU 来说是较大的浪费,一般只有特定场景下才使用。
比如当调用 recvfrom 函数以非阻塞方式从某个套接字上读取数据时,如果底层数据还没有准备好,那么 recvfrom 函数会立马错误返回,而不会让该进程或线程进行阻塞等待。
阻塞 IO 和非阻塞 IO 的区别在于,阻塞 IO 当数据没有就绪时,后续检测数据是否就绪的工作是由操作系统发起的,而非阻塞 IO 当数据没有就绪时,后续检测数据是否就绪的工作是由用户发起的。
实现方式
实现非阻塞式 IO 的方式有很多,包括可以设置 send、recv 函数的 flags 参数为 MSG_DONTWAIT,但我们最推荐的还是一劳永逸的做法,比如以下两种方式。
(1)在打开文件时设置非阻塞读取
打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用 open 函数打开文件时携带 O_NONBLOCK 或 O_NDELAY 选项,此时就能够以非阻塞的方式打开文件。
(2)将已经打开的某个文件或套接字设置为非阻塞读取
需要用到 fcntl 函数:
int fcntl(int fd, cmd, ... );





