个人博客:www.cyhone.com
公众号:编程沉思录
在进行Linux网络编程开发的时候,免不了会涉及到IO模型的讨论。《Unix网络编程》一书中讨论的几种IO模型,我们在开发过程中,讨论最多的应该就是三种: 阻塞IO
、非阻塞IO
以及异步IO
。
本文试图理清楚几种IO模型的根本性区别,同时分析了为什么在Linux网络编程中最好要用非阻塞式IO?
在讨论网络IO之前,一定要有一个概念上的准备前提: 不要用操作磁盘文件的经验去看待网络IO。 具体的原因我们在下文中会介绍到。
相比于传统的网络IO来说,一个普通的文件描述符的操作可以分为两部分。以read
为例,我们利用read函数从socket中同步阻塞的读取数据,整个流程如下所示:
可以看到除了转入内核调用,与传统的磁盘IO不同的是,网络IO的读写大致可以分为两个阶段:
我们日常开发遇到最多的三种IO模型分别是:同步阻塞IO、同步非阻塞IO、异步IO。
这些名词非常容易混淆,为什么一个IO会有两个限定词:同步和阻塞?同步和阻塞分别代表什么意思?
简单来说:
所谓异步,实际上就是非同步非阻塞。
read(fd, buffer, count)
Linux下面如果直接不对fd进行特殊处理,直接调用read,就是同步阻塞IO。同步阻塞IO的两个阶段都需要等待完成后,read才会返回。
也就是说,如果远程一直没有发送数据,则read一直就不会返回,整个线程就会阻塞到这里了。
对于同步非阻塞IO来说,如果没有可读可写事件,则直接返回;如果有,则进行第二个阶段,复制数据。
在linux下面,需要使用fcntl将fd变为非阻塞的。
int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, flags | O_NONBLOCK);
同时,如果read的时候,fd不可读,则read调用会触发一个EWOULDBLOCK错误。用户只要检查下errno == EWOULDBLOCK
, 即可判断read是否返回正常。
基本在Linux下进行网络编程,非阻塞IO都是不二之选。
Linux开发者应该很少使用纯粹的异步IO。因为目前来说,Linux并没有一个完美的异步IO的解决方案。pthread虽然提供了aio的接口,但是这里不做太具体的讨论了。
我们平常接触到的异步IO库或者框架都是在代码层面把操作封装成了异步。但是在具体调用read或者write的时候,一般还是用的非阻塞式IO。
为什么不能用操作磁盘IO的经验看待网络IO。实际上在磁盘IO中,等待阶段是不存在的,因为磁盘文件并不像网络IO那样,需要等待远程传输数据。
所以有的时候,习惯了操作磁盘IO的开发者会无法理解同步阻塞IO的工作过程,无法理解为什么read函数不会返回。
关于磁盘IO与同步非阻塞的讨论,在知乎上有一篇帖子为什么书上说同步非阻塞io在对磁盘io上不起作用? 讨论了这个问题。
上文说到,在linux网络编程中,如果使用阻塞式的IO,假如某个fd长期不可读,那么一个线程相应将会被长期阻塞,那么线程资源就会被白白浪费。
那么,如果我们使用IO多路复用例如epoll去监听fd的可读事件呢? 因为如果使用epoll监听了fd的可读事件,在epoll_wait之后调用read,此时fd一定是可读的, 那么此时非阻塞IO相比于阻塞IO的优势不就没了。
实际上,并不是这样的。epoll也必须搭配非阻塞IO使用。
这个帖子为什么 IO 多路复用要搭配非阻塞 IO? 详细讨论了这个问题?
总结来说,原因有二:
因此,在Linux网络编程中最好使用非阻塞式IO。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。