今天code review时,同事B对我代码中的poll()的处理做法提出了异议。于是做了些研究,还发现了一些好玩的故事。

异议的代码

我的代码是参考manpage写的,类似下面的做法。同事B说没有处理POLLERR、而且应当使用else if

OK。我赞同补充POLLERR的处理,但不赞同使用else if。原因:

  • fd的读事件、写事件可能会同时到达,因此我想同时处理这两个事件;
  • Linux Manpage里面的示例,就是三个if语句独立的。
ret = poll(fds, 2, timeout_msecs);
if (ret > 0) {
/* An event on one of the fds has occurred. */
for (i=0; i<2; i++) {
if (fds[i].revents & POLLIN ) {
/* Priority data may be written on device number i. */
...
}
if (fds[i].revents & POLLOUT ) {
/* Data may be written on device number i. */
...
}
}
}

诡异的经历

但是同事B举出了他偶然体验到的诡异经历:

POLLIN, POLLOUT, POLLERR同时出现。

在这种异常下,我的代码处理逻辑就会坑爹了。

于是问题变成了,什么情况下会出现这种诡异场景、三个事件同时出现究竟是什么含义?

翻阅《UNIX环境高级编程》、《UNIX网络编程》里面对poll()的讲解,均没有提到信号是否会同时出现的问题(所以也没提到该不该用else if的事情了)。

在Github上查找POLLERR相关的代码,发现大多数人都是用3个if语句处理这三个事件。那真相究竟是啥?

牛人的解答

百般搜索,终于在StackOverflow.com上看到有人提到了一个相似的问题:

Sometimes epoll_wait returns with both POLLOUT & POLLERR events set for the same socket descriptor.

终于下面有大神做了解答

Here is some good information on non-blocking tcp connect().

When a socket error is detected (i.e. connection closed/refused/timedout), epoll will return the registered interest events POLLIN/POLLOUT with POLLERR. So epoll_wait() will return POLLOUT|POLLERR if you registered POLLOUT, or POLLIN|POLLOUT|POLLERR if POLLIN|POLLOUT was registered.

Just because epoll returns POLLIN doesn't mean there will be data available to read, since recv() may just return the error from the non-blocking connect() call. I think epoll returns all the registered events with POLLERR to make sure the program calls send()/recv()/etc.. and gets the socket error. Some programs never check for POLLERR/POLLHUP and only catch socket errors on the next send()/recv() call.

翻译一下:

这儿有些很赞的关于非阻塞TCP connect()的信息。

当一个socket出现错误时(例如 连接断开/拒绝/超时),epoll()会返回POLLERR加上注册时的POLLIN/POLLOUT事件。所以,如果监听的是POLLOUT,那epoll_wait()会返回POLLOUT|POLLERR;如果监听的是POLLIN,那epoll_wait()会返回POLLIN|POLLERR。

注意epoll()返回POLLIN并不表示会有数据可读,因为recv()会立刻返回前一个错误码(即非阻塞的connect()调用)。我个人认为epoll()返回所有的注册事件加POLLERR,是为了确保程序会调用send()/recv()等等,进而发现socket出错了。毕竟有些代码从来不检测POLLERR/POLLHUP,只折腾send()/recv()等函数的错误码。

呵呵,Github上翻看了这么多代码,的确是大神说的样子。

验证

所以同事B的经历是常见的场景。而且很容易就能够触发。只要在连接上闹些问题,就能达到目的了。例如下面这段代码演示了连接失败时,POLLERR/POLLIN/POLLOUT事件都同时触发了。

示例中使用了getsockopt()来获取错误码;也可以直接使用read()/write()也是能够获取相同的错误码。

深入探究

StackOverflow的大神只做了简要的解答。真正的原因只能自己去翻看代码了。

翻阅内核代码(我的系统版本是Linux-2.6.32.57-x86 ),可以看到在tcp_poll()里(net/ipv4/tcp.c的389行,我的场景是TCP),对于所有sock错误都置了POLLERR。而异常情况下,POLLIN/POLLOUT则分别与RCV_SHUTDOWN/SEND_SHUTDOWN有关。换个视角,和连接断开有关的代码在tcp_reset()中(net/ipv4/tcp_input.c的3957行)的处理,里面的tcp_done()代码)则明确设置了sk->sk_shutdown = SHUTDOWN_MASK——所以,对于关闭的连接,总是会有POLLIN/POLLOUT事件!

研究到此解决。真相大白。

所以啊,我还是听取同事B的建议,加个else if优化一下处理逻辑吧。

epoll_wait 时 POLLERR 与 POLLIN 同时返回的现象解析(转)的更多相关文章

  1. mysql 查询数据时按照A-Z顺序排序返回结果集

    mysql 查询数据时按照A-Z顺序排序返回结果集 $sql = "SELECT * , ELT( INTERVAL( CONV( HEX( left( name, 1 ) ) , 16, ...

  2. ASP调用WEBSERVICE并对返回结果进行解析时遇到的问题

    项目上用动易平台做新闻发布网站,动易平台是用ASP做的,期间需要根据当前登录的用户,取其他系统比如OA的待办事项进行列表展示,OA组的同事给了我一个WSDL接口,百度了很多ASP调用webservic ...

  3. 处理内容有&特殊字符thinkphp返回xml无法解析的问题<![CDATA[xxx]]>

    处理内容有&特殊字符thinkphp返回xml无法解析的问题<![CDATA[xxx]]> // xml 转义特殊字符 如&'" <![CDATA[&quo ...

  4. 单片机中用c编程时头文件reg51.h及reg52.h解析

    单片机中用c编程时头文件reg51.h及reg52.h解析 我们在用c语言编程是往往第一行就是reg51.h或者其他的自定义头文件,我们怎么样来理解呢? 1)“文件包含”处理. 程序的第一行是一个“文 ...

  5. json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来。

    json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来.

  6. 【转】使用Mybatis时遇到的延迟加载造成返回异常的问题——HttpMessageConversionException: Type definition error

    在使用Mybatis的过程中,使用了resultMap延迟加载. 延迟加载:association联表查询的过程中,查询另外两个表的对象.而延迟加载是指只有在使用这两个对象的时候才会进行查询. 问题的 ...

  7. MyBatis 返回类型resultType为map时的null值不返回问题

    问题一:    查询结果集中 某字段 的值为null,在map中不包含该字段的key-value对 解决:在mybatis.xml中添加setting参数 <!-- 在null时也调用 sett ...

  8. Spring中抛出异常时,既要要返回错误信息,还要做事务回滚

    情况一:如果没有在程序中手动捕获异常,如下代码事务会回滚 情况二:如果在程序中自已捕获异常未往外抛,如下代码事务不会回滚 如果doDbStuff2()这个操作数据库的方法抛出异常,因为将异常捕获未往外 ...

  9. 使用Hybris Commerce User API读取用户信息时,电话字段没有返回

    在使用Hybris Commerce User API读取一个user信息时,我遇到一个问题,在API返回的结构里没有包含期望看到的Phone字段. 仔细观察Swagger里对response结构的说 ...

随机推荐

  1. MariaDB基础命令

    关闭防火墙和selinux #systemctl stop firewalld 安装服务端和客户端mariadb # yum install mariadb-server mariadb -y mar ...

  2. [UE4]删除UI:Remove from Parent

    同时要将保存UI的变量清空,以释放占用的系统内存

  3. centos7 安装pip+python3.6

    centos7安装pip 1.执行:yum install python-pip 若没有python-pip包,先执行:yum -y install epel-release,再执行yum insta ...

  4. 20165312 预备作业3 Linux安装及学习

    Linux安装及学习 一.安装Vmware虚拟机以及Ubuntu中遇到的问题 因为之前安装过Vmware虚拟机,考虑到两者没有太大的差别,就所以就没有再安装Vbox虚拟机. 在安装Vmware虚拟机增 ...

  5. 《Java 程序设计》课堂实践项目 课后学习总结

    <Java 程序设计>课堂实践项目 课后学习总结 String类的使用(sort) 目录 Linux命令(sort) 课堂实践 课后思考 学习老师的代码之后的思考:int与Integer ...

  6. 0000 - Spring MVC 原理以及helloworld

    1.概述 Spring MVC是目前最好的实现MVC设计模式的框架,是Spring框架的一个分支产品.以Spring IOC容器为基础,并利用容易的特性来简化它的配置.Spring MVC相当于Spr ...

  7. a++ 与 ++a 的运算

    var a=5: b=a++和b=++a的区别: 前者是先赋值,再自加,即b=a:a=a+1: //结果b=5,a=6 后者是先自加,再赋值,即a=a+1;b=a;  //结果a=6,b=6

  8. VS远程调试与附加调试

    使用场景 假如你要开发的程序需要运行在独特的硬件上,比如:服务器,然而这个"独特的硬件"却不能给你装VS,这时候远程调试就登场了. 测试工具/环境一览 Key Value VS版本 ...

  9. uWSGI, Gunicorn, 啥玩意儿?

    因为nginx等优秀的开源项目,有不少本来不是做服务器的同学也可以写很多服务器端的程序了.但是在聊天中会发现,大家虽然写了不少代码,但是对wsgi是什么,gunicorn是什么,反向代理又是什么并不了 ...

  10. 2-Zookeeper、HA安装

    1.Zookeeper安装 1.解压 zookeeper 到安装目录中/opt/app/zookeeper 中. 2.在安装目录下创建data和logs两个目录用于存储数据和日志: cd /opt/a ...