同步非阻塞式网络 IO 模型详解 同步非阻塞 IO 原理上文中探讨了同步阻塞式网络 IO 模型,本文将讨论另一种模型:同步非阻塞 IO 模型。
同步非阻塞 IO(non-blocking IO):应用进程发起 IO 系统调用后,内核立即返回给用户一个状态值,不需要等到 IO 操作彻底完成。比如发生 IO 操作 read 时,它会经历两个阶段:
-
第一个阶段,等待数据准备就绪:
当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。
-
第二个阶段,将数据从内核拷贝到用户进程中:
一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回正确的返回值。
所以,在非阻塞式 IO 中,用户进程其实是需要不断的主动询问 kernel 数据准备好了没有。非阻塞的接口相比于阻塞型接口的显著差异在于被调用之后立即返回。
产生的问题:不断轮询 kernel 数据准备是否准备就绪将大幅度推高 CPU 占用率;此外,在这个方案中 read() 更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如 select()多路复用模式,可以一次检测多个连接是否活跃,详细可以参考本系列的后续文章。
代码实现Linux 下,可以通过文件描述符设置使的 Socket API 变为 non-blocking。具体可以使用如下的函数将某句柄(套接字文件描述符) fd 设为非阻塞状态:
#include#include int fcntl(int fd, int cmd, ... ); 功能:改变已打开的文件性质,fcntl针对描述符提供控制。 参数: fd:操作的文件描述符 cmd:操作方式 arg:针对cmd的值,fcntl能够接受第三个参数int arg。 返回值: 成功:返回某个其他值 失败:-1 其中 fcntl 函数有5种功能: 1) 复制一个现有的描述符(cmd=F_DUPFD) 2) 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD) 3) 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL) 4) 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN) 5) 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW) // 获取原来的flags int flags = fcntl(fd, F_GETFL); // 获取新的特性 flag |= O_NONBLOCK; // 位或:表示追加的方式,等价于 flags = flags | O_NONBLOCK; // 设置新的flags fcntl(fd, F_SETFL, flags); //等价于 fcntl( fd, F_SETFL, O_NONBLOCK );
在非阻塞状态下,read() 接口在被调用后立即返回,返回值代表了不同的含义。如在本例中
- read() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;
- read() 返回 0,表示连接已经正常断开;
- read() 返回 -1,且 errno 等于 EAGAIN,表示 read 操作还没执行完成;
- read() 返回 -1,且 errno 不等于 EAGAIN,表示 read 操作遇到系统错误 errno。
详细代码示例参考 GitHub 开源代码 “NIO"部分,下载链接
NIO 总结- NIO 网络模型在等待数据阶段是非阻塞状态,所以相对于上一篇文章中的 BIO,其在一定程度上提高服务端的并发量,但是这是在消耗 CPU 资源不断轮询的基础上获得的;
- 实际场景中并不会单独使用 NIO 网络模型,但是设置通讯网络为非阻塞的思路将与其他网络模型(比如之后的文章会详细介绍的 epoll)搭配使用以提高模型的并发处理能力。
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:服务器课程



