22 | 非阻塞I/O:提升性能的加速器
22 | 非阻塞I/O:提升性能的加速器
讲述:冯永吉
时长10:27大小9.56M
阻塞 VS 非阻塞
非阻塞 I/O
读操作
写操作
accept
connect
非阻塞 I/O + select 多路复用
实验
总结
思考题
赞 10
提建议
精选留言(64)
- HerofH2020-01-14老师您好!我看到您这在应用层设计了一个读写缓冲区,我之前看了muduo和libevent,也是设计了这样的缓冲区,并且muduo作者陈硕也提到非阻塞IO必须要设计一个应用层Buffer,我很疑惑的就是,这样的读写缓冲区的必要性是什么呢? 我大概只能理解到非阻塞IO下使用应用层写缓冲区可以让还未来得及发出的数据先保存在应用层Buffer中,然后等到可写的时候再将数据从应用层Buffer写到fd的发送缓冲区中; 那么如何理解应用层读缓冲区的必要性呢?有数据来,触发可读事件,这个时候直接调用read去读不就可以了吗,为什么一定要先读到读缓冲区呢?根据libevent中,每次读数据时都会尽量多的从fd的接收缓冲区中读取数据到应用层buffer,我的一种想法是,设置读缓冲区的作用,是否是为了减少read的调用次数呢? 还是有其它原因呢?想了解一下老师的看法,谢谢!展开
作者回复: 读缓冲的作用有很多,你提到了通过设置缓冲区,减少系统调用的次数是一个方面,另外,别忘了读取的数据是需要在应用层进行报文解析的,一个应用层缓冲区显然是比较方便的,否则,需要不断的进行数据的读取,直至解析到完整的报文。我认为,应用层缓冲是"空间换时间"的一个比较好的例子。
33 - TinyCalf2020-11-08我在思考一个问题,select既然已经告诉我们接口可读了,为什么还要用非阻塞IO;我自己的想法是,select其实只通知了有没有内容可读,没有提供有多少数据可读,所以当我们使用阻塞IO循环read时,无法确认下一个read还是不是可读的,因此仍然可能阻塞,而非阻塞IO可以解决这个问题,不知道我想的对不对
作者回复: 我觉得是可以这样理解的。
共 2 条评论14 - MoonGod2019-09-27感觉这篇的解释和前面的比起来太不细致了…很多地方都没说明。老师能不能多一些说明啊
作者回复: 我在代码里多加一些注释,可以看最新的代码 https://github.com/froghui/yolanda
共 4 条评论12 - fedwing2020-08-13那么非阻塞 I/O 呢?你去了书店,问老板有没你心仪的那本书,老板查了下电脑,告诉你没有,你就悻悻离开了。一周以后,你又来这个书店,再问这个老板,老板一查,有了,于是你买了这本书。注意,这个过程中,你没有被阻塞,而是在不断轮询。但轮询的效率太低了,于是你向老板提议:“老板,到货给我打电话吧,我再来付钱取书。”这就是前面讲到的 I/O 多路复用。 对于这个我有点疑问,select和poll本质上,不都是轮询吗,为什么这里说轮询效率太低,改成select,poll展开
作者回复: select和poll是"老板"这个内核自己在轮询哦,不是买书人(应用程序)在轮询,"老板"可以有多个方式优化这个过程,比如他记录了一个事件,一旦书定成了,他就发现需要告诉和他有订阅关系的"你"了。想想看,作为应用程序的"你"在这个过程中,是不是可以放开手干点别的事,比如玩个吃鸡游戏的什么。
共 4 条评论9 - 衬衫的价格是19美元2020-07-121.select,poll,epoll是io多路复用技术,是操作系统提供的检测io事件是否就绪的方法,当然我们可以不用操作系统提供的方法而自己去写一个轮训,但是轮训会加重cpu负载。 2.当我们调用fcntl将套接字配置为非阻塞后,在该套接字上后续的accept,read,write操作都将变为非阻塞 3.非阻塞io一般都需要配合io多路复用技术使用
作者回复: 总结到位👍
共 3 条评论10 - 扩散性百万咸面包2020-06-06老师,认真的问你一个问题。 Redis中,网上的介绍是说单线程 + 多路复用 + 非阻塞I/O。为什么不采取C10k问题中的,主从Reactor结构,多个事件分发器来充分利用CPU的多核能力呢?理论上这样更好啊。
作者回复: 很认真的回答你,我觉得你说的 单线程 + 多路复用 + 非阻塞I/O,是和redis的设计有关的,通过这么一个设计,减少了多个线程锁的消耗,我想是redis做了一些取舍的。
共 2 条评论9 - 绿箭侠2020-02-28老师,评论区中 程序水果宝 说select非阻塞,我的理解:select(maxfd + 1, &readset, &writeset, &exset, NULL) 此处因为NULL当然阻塞,不明白为什么说select非阻塞??
作者回复: 非阻塞和阻塞是指I/O,具体作用到的是套接字上,而select这里是I/O多路复用的一种技术,注意到这一行 : make_nonblocking(fd); 实际上是把套接字都改为非阻塞I/O,再通过I/O多路复用来接收套接字上的I/O事件。
共 3 条评论5 - javaYJL2019-11-05老师,accept()这个函数不是阻塞的吗?
作者回复: accept和阻塞套接字一起使用就是阻塞的,和非阻塞套集字一起使用就是非阻塞的。阻塞和非阻塞是作用到套集字上的。
5 - 一天到晚游泳的鱼2019-10-05老师,我想请教一个问题就是, 当把一个描述符设置为非阻塞的之后,在该描述符上面的操作就会变成非阻塞的吗? 比如说把连接套接字设置为非阻塞的,send和recv就会变成非阻塞的吗?
作者回复: 你的理解是对的。
共 2 条评论5 - yusuf2019-09-291、133行判断是否超过了文件描述符的最大值,如果超过了,就会报错。可以考虑使用动态分配的方式,但如果超过了1024的话,使用上一节中的poll来处理会更好些 2、认为是考虑到对同一个fd的同一缓冲区进行读写操作,只用一个Buffer对象足够了5
- YUAN2020-10-03select和poll也是轮训吧😄?
作者回复: 我把它归为多路复用。轮询的意思是应用程序自己不断的从应用层发起检测。
4 - null2021-04-09Buffer.readable 为 1 或 0: 1-可读,说明结构体 buffer 有数据可读,也就可以往 connect_fd 写数据,因此是监听 connect_fd 的写事件。 0-不可读,说明结构体 buffer 无数据,即结构体 buffer 有空间可以写数据,也就可以从 connect_fd 读数据,因此是监听 connect_fd 的读事件。 老师,是这样不,谢谢!展开
作者回复: 鼓掌👏
3 - skye2020-01-09read 总是在接收缓冲区有数据时就立即返回,不是等到应用程序给定的数据充满才返回。=== 老师,这个是指阻塞时吗?
作者回复: 这句话是说,在read调用时,虽然设定需要1k个字节,实际上总是在缓冲区有数据时就立即返回了,有可能只读到了20个字节。这种行为对阻塞和非阻塞是一样的。
3 - 沉淀的梦想2019-09-30老师代码中进行rot13_char编码的目的是啥?
作者回复: ROT13(回转13位,rotateby13places,有时中间加了个减号称作ROT-13)是一种简易的置换暗码。 ROT-13 编码是一种每一个字母被另一个字母代替的方法。这个代替字母是由原来的字母向前移动 13 个字母而得到的。数字和非字母字符保持不变。 它是一种在网路论坛用作隐藏八卦、妙句、谜题解答以及某些脏话的工具,目的是逃过版主或管理员的匆匆一瞥。ROT13激励了广泛的线上书信撰写与字母游戏,且它常于新闻群组对话中被提及。
3 - 石将从2019-09-28没有注释,看onSocketWrite和onSocketRead函数很费劲
作者回复: 其实还是蛮简单的,稍微解释一下: onSocketRead是通过套接字读取数据,数据存放在Buffer对象里,Buffer对象通过了writeIndex记录当前数 据区可写的位置; onSocketWrite通过套接字写数据,数据来源于Buffer缓冲对象,Buffer缓冲对象的readIndex记录了当前缓冲区读的位置。
3 - keepgoing2020-08-07请问老师onSocketRead函数中,error != EAGAIN的情况不应该也返回1,表示读到了错误需要关闭吗。 我理解recv == 0 以及 < 0的情况都需要关闭,这里为什么只判断 == 0的情况关闭呢,另外结果返回值r == -1一直没有处理,想问下这个-1是什么用呢。看完代码比较疑惑,想请老师解答一下
作者回复: 如果是recv == 0,则说明是EOF,正常关闭; 如果是recv <0,说明出错了,不做任何处理,继续下个fd; 如不是recv <0,但是为EAGAIN,则进入写判断; -1的作用就是为了区别出>0和==0的不同return值。
共 3 条评论2 - J.M.Liu2019-10-04老师,有一个地方不是很明白。在连接套接字设为阻塞时,当客户端发送RST后,服务端在已完成连接队列删除了连接,accept阻塞。这时候如果有新连接进来了,为什么accept还是会阻塞呀?难道新连接进来一定要先select之后,accept才能取到连接好的套接字??
作者回复: 不是一定需要select才可以 accept的。这里的例子主要是说明如果不给监听套集字设置为非阻塞,可能会引起的问题。
共 3 条评论2 - 沉淀的梦想2019-09-30老师那个accept阻塞的实验,在我电脑(linux-4.18.0,ubuntu18.10)上的行为有点不太一样,无论是把listen_fd设置为阻塞还是非阻塞,sleep 5s还是10s或者更长,行为总是:accept成功获取到客户端连接,然后读取到客户端的RST
作者回复: 这里举的一个例子,有可能在新的协议栈下有所改变,但是不管怎样,理论上的分析有可能导致阻塞,所以我们还是应该在平时编码中将监听套接字也设置为非阻塞的。
共 4 条评论2 - Jaime2021-04-16第一道问题,说明了这个服务器只能服务固定的连接,LINUX上是1024,改进的化,可以使用poll或者epoll突破文件描述符数量限制 第二道问题,BUFF就是一个循环缓冲区,一个读指针,一个写指针,已经可以实现读写功能,目的是节省内存
作者回复: 👍
1 - null2021-04-09原文: 那么非阻塞 I/O 呢?…,这个过程中,你没有被阻塞,而是在不断轮询。 但轮询的效率太低了,于是你向老板提议:“老板,到货给我打电话吧,我再来付钱取书。”这就是前面讲到的 I/O 多路复用。 以下代码片段,摘自前面两节课,当 select/poll 的 time 参数设置为 0,也是非阻塞立刻返回,然后不断轮询调用 select/poll 函数。这不就和非阻塞 I/O 描述的是一样:立刻返回+轮询。 是不是说 select/poll 可以处理多条“链路”,而非阻塞 I/O 只处理一条链路。还是说这两个概念不是一回事,水果和动物不能混为一谈。 请问老师,是我哪里理解错了么?谢谢! select/poll 多路复用代码段: for (;;) { readmask = allreads; int rc = select(socket_fd + 1, &readmask, NULL, NULL, NULL); } for (;;) { if ((ready_number = poll(event_set, INIT_SIZE, -1)) < 0) { error(1, errno, "poll failed "); } }展开
作者回复: select和poll是让操作系统在有I/O实际时再从调用中返回,而且本质上select和poll都是在等待多种I/O事件,和前面每次轮询单个I/O事件是否就绪,有天壤之别。
1