POLLERR的故事
今天code review時,同事B對我代碼中的poll()的處理做法提出了異議。于是做了些研究,還發現了一些好玩的故事。
異議的代碼
我的代碼是參考manpage寫的,類似下面的做法。同事B說沒有處理POLLERR、而且應當使用else if。
OK。我贊同補充POLLERR的處理,但不贊同使用else if。原因:
- fd的讀事件、寫事件可能會同時到達,因此我想同時處理這兩個事件;
- Linux Manpage里面的示例,就是三個if語句獨立的。
詭異的經歷
但是同事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優化一下處理邏輯吧。
總結
以上是生活随笔為你收集整理的POLLERR的故事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 传说中的萝卜散热 是“图吧老哥”们的精神
- 下一篇: Asterisk拨号方案中变量的应用