歡迎關注github:
### 背景
銀時跟我講,想從 netty3遷移到netty4 。
問其原因是因為 netty3在容器裡會報錯,錯誤堆棧:
java.io.ioexception: 無法立即完成一個非阻止性套接字操作。
at sun.nio.ch.socketdispatcher.close0(native method)
at sun.nio.ch.socketdispatcher.preclose(socketdispatcher.java:44)
at sun.nio.ch.socketchannelimpl.implcloseselectablechannel(socketchannelimpl.java:677)
at java.nio.channels.spi.abstractselectablechannel.implclosechannel(abstractselectablechannel.java:201)
at java.nio.channels.spi.abstractinterruptiblechannel.close(abstractinterruptiblechannel.java:97)
### 分析
看到這個問題,之前我也沒有遇到過,不過如果 netty3有這個問題,netty4應該也會存在。那就看看到底什麼導緻這個問題。
找到 socketdispatcher的close0 方法,這是個本地方法:
找到 windows的實作:
windows平台通過調用closesocket( winsock2.h)關閉套接字。
接着檢視巨硬的 ,内容摘要:
if no error occurs, closesocket returns zero. otherwise, a value of socket_error is
returned, and a specific error code can be retrieved by calling .
意思就是如果正确傳回 0,如果錯誤傳回socket_error。并且通過 函數擷取錯誤狀态。
這裡很明顯是發生出錯誤向上抛出了異常。
通過分析 closesocket的錯誤狀态資訊以及谷歌“無法立即完成一個非阻止性套接字操作”,确診為錯誤狀态 。
閱讀 ,得知該錯誤狀态是設定了 so_linger所緻。
于是回過頭去看下代碼,果然,圖為截取的 netty代碼片段:
巨硬的文檔是這麼說的:
setting the l_onoff member of the structure
to nonzero and the l_linger member with a nonzero timeout interval on a nonblocking socket is not recommended.
意思是,在非阻塞的 socket情況下不建議設定so_linger參數。
in this case, the call to closesocket will
fail with an error of if
the close operation cannot be completed immediately. if closesocket fails
with the
socket handle is still valid, and a disconnect is not initiated. the application must call closesocket again to close the socket.
如果設定了 so_linger,并且制定了逾時時間,這時,我們調用 closesocket方法,方法不能立即完成的話,會抛出 錯誤。但是,這個 socket此時還是有效的,可以一段時間之後再次調用 close方法進行關閉嘗試。
### 解決方法
但是 java抛出簡單ioexception ,我們無法判斷是否為 錯誤。很難判斷是否是因為其他原因導緻的 ioexception,是以不可能進行重試。
最終,解決方法去掉
這行代碼。
改進之後,在調用 close方法時,不會抛出異常并且在底層 socket關閉前,系統會盡可能的把将緩沖隊列的資料發送給對端。原文如下:
if the l_onoff member of the structure
is zero on a stream socket, the closesocket call will return immediately and does not receive whether the socket is blocking or nonblocking. however, any data queued for transmission will be sent, if possible, before the underlying socket is closed.
### 總結
在使用nio 的時候,最好不要配置 so_linger,如果設定了該參數,在 close的時候如緩沖區有資料待寫出,會抛出 ioexception。
後記:最近銀時發現,zookeeper之前的版本也是有設定這個參數,并且在最新版本去掉了這個參數,難道大神們的代碼也是ctrl+c,ctrl+v過來的。呵呵。
### 參考資料