连接假死

原因

  • 网络设备出现故障,例如网卡,机房等,底层的 TCP 连接已经断开了,但应用程序没有感知到,仍然占用着资源。
  • 公网网络不稳定,出现丢包。如果连续出现丢包,这时现象就是客户端数据发不出去,服务端也一直收不到数据,就这么一直耗着
  • 应用程序线程阻塞,无法进行数据读写

问题

  • 假死的连接占用的资源不能自动释放
  • 向假死的连接发送数据,得到的反馈是发送超时

检测空闲连接以及超时对于及时释放资源来说是至关重要的。由于这是一项常见的任务, Netty 特地为它提供了几个ChannelHandler 实现。

IdleStateHandler

当连接空闲时间太长时,将会触发一个 IdleStateEvent 事件。然后,你可以通过在你的 ChannelInboundHandler 中重写 userEventTriggered()方法来处理该IdleStateEvent 事件。

ReadTimeoutHandler

如果在指定的时间间隔内没有收到任何的入站数据,则抛出一个Read-TimeoutException 并关闭对应的 Channel。可以通过重写你的 ChannelHandler 中的exceptionCaught()方法来检测该 Read-TimeoutException。

WriteTimeoutHandler

如果在指定的时间间隔内没有任何出站数据写入,则抛出一个Write-TimeoutException 并关闭对应的 Channel 。可以通过重写你的 ChannelHandler 的exceptionCaught()方法检测该 WriteTimeout-Exception。

服务器端解决

  • 怎么判断客户端连接是否假死呢?如果能收到客户端数据,说明没有假死。因此策略就可以定为,每隔一段时间就检查这段时间内是否接收到客户端数据,没有就可以判定为连接假死
    1. // 用来判断是不是 读空闲时间过长,或 写空闲时间过长
    2. // 5s 内如果没有收到 channel 的数据,会触发一个 IdleState#READER_IDLE 事件
    3. ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
    4. // ChannelDuplexHandler 可以同时作为入站和出站处理器
    5. ch.pipeline().addLast(new ChannelDuplexHandler() {
    6. // 用来触发特殊事件
    7. @Override
    8. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
    9. IdleStateEvent event = (IdleStateEvent) evt;
    10. // 触发了读空闲事件
    11. if (event.state() == IdleState.READER_IDLE) {
    12. log.debug("已经 5s 没有读到数据了");
    13. ctx.channel().close();
    14. }
    15. }
    16. });

    客户端定时心跳

    1. // 用来判断是不是 读空闲时间过长,或 写空闲时间过长
    2. // 3s 内如果没有向服务器写数据,会触发一个 IdleState#WRITER_IDLE 事件
    3. ch.pipeline().addLast(new IdleStateHandler(0, 3, 0));
    4. // ChannelDuplexHandler 可以同时作为入站和出站处理器
    5. ch.pipeline().addLast(new ChannelDuplexHandler() {
    6. // 用来触发特殊事件
    7. @Override
    8. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
    9. IdleStateEvent event = (IdleStateEvent) evt;
    10. // 触发了写空闲事件
    11. if (event.state() == IdleState.WRITER_IDLE) {
    12. // log.debug("3s 没有写数据了,发送一个心跳包");
    13. ctx.writeAndFlush(new PingMessage());
    14. }
    15. }
    16. });