NIO设置SO_LINGER引发的异常

欢迎关注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 的错误状态信息以及谷歌“无法立即完成一个非阻止性套接字操作”,确诊为错误状态  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过来的。呵呵。偷笑

### 参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值