歡迎關注Github:https://github.com/teaey/
### 背景
銀時跟我講,想從 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 WSAGetLastError.
意思就是如果正確返回 0,如果錯誤返回SOCKET_ERROR。並且通過 WSAGetLastError函數擷取錯誤狀態。
這裡很明顯是發生出錯誤向上拋出了異常。通過分析 closesocket的錯誤狀態資訊以及Google“無法立即完成一個非阻止性通訊端操作”,確診為錯誤狀態 WSAEWOULDBLOCK 。
閱讀 官方文檔,得知該錯誤狀態是設定了 SO_LINGER所致。
於是回過頭去看下代碼,果然,圖為截取的 Netty程式碼片段:
巨硬的文檔是這麼說的:
Setting the l_onoff member of the linger 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 WSAEWOULDBLOCK if the close operation cannot be completed immediately. If closesocket fails with WSAEWOULDBLOCK 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方法,方法不能立即完成的話,會拋出 WSAEWOULDBLOCK 錯誤。但是,這個 socket此時還是有效,可以一段時間之後再次調用 close方法進行關閉嘗試。
### 解決方案
但是 java拋出簡單IOException ,我們無法判斷是否為 WSAEWOULDBLOCK 錯誤。很難判斷是否是因為其他原因導致的 IOException,所以不可能進行重試。
最終,解決方案去掉
這行代碼。
改進之後,在調用 close方法時,不會拋出異常並且在底層 socket關閉前,系統會儘可能的把將緩衝隊列的資料發送給對端。原文如下:
If the l_onoff member of the LINGER 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過來的。呵呵。
### 參考資料
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms737582(v=vs.85).aspx
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms741580(v=vs.85).aspx
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx#WSAEWOULDBLOCK