为什么 netty 客户端表现得像僵尸?

Why a netty client performs as zombie?

我使用 netty 作为套接字客户端:

public void run() {
    isRunning = true;
    EventLoopGroup group = new NioEventLoopGroup(EventLoopsPerGetter);
    Bootstrap b = new Bootstrap();
    b.group(group).channel(NioSocketChannel.class)
     .handler(new ChannelInitializer<SocketChannel>() {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline p = ch.pipeline();
            p.addLast(
                    new ProtobufVarint32FrameDecoder(),
                    ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP),
                    new ProtobufDecoder(Protocol.Packet.getDefaultInstance()),

                    new ProtobufVarint32LengthFieldPrepender(),
                    ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP),
                    new ProtobufEncoder(),

                    session
                    );
        }
    });
    try {
        while(isRunning) {
            try {
                 b.connect(host, port).sync().channel().closeFuture().sync();
            } catch(Exception e) {
                if (e instanceof InterruptedException) {
                    throw e;
                }
                retryLogger.warn("try to connect to " + host + " : " + port + " , but", e);
            }
            if(isRunning) {
                retryLogger.info("netty connection lost, retry!");
                Thread.sleep(RetryInterval);
            }
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        group.shutdownGracefully();
    }
}

session代码很简单,发送Get-packet到服务器,得到响应,写入文件,然后发送下一个Get-packet。

在这个程序中,我启动了两个 netty 客户端线程,但是 运行 几天后,其中一个表现得像僵尸线程,也就是说,即使我杀死了 netty 服务器,僵尸客户端也会打印没有日志,而另一个客户端打印想要的日志。顺便说一下,jstack 文件显示两个线程都处于活动状态,而不是死机。

我正在使用 netty 5.

您没有任何读取超时机制,发生的情况是 10~(取决于路由器型号)分钟内没有流量,并且路由器中的 NAT table 认为连接正常完成,并关闭连接。

您有多种方法可以解决这个问题:

使用ReadTimeoutHandler

ReadTimeoutHandler 关闭通道并抛出一个 ReadTimeoutException if a timeout is detected. You can catch this exception if needed via the exceptionCaught。以你现有的逻辑,你不需要捕捉这个。

此处理程序还可以与 WriteTimeoutHandler 结合使用,以将 "ping" 消息写入远程。但是,以下解决方案更适合此目的。

使用IdleStateHandler

您也可以为此使用 IdleStateHandler,此处理程序有 3 个参数,分别代表 readerIdleTimewriteIdleTimeallIdleTime。这个 class 的优点是它不会抛出异常并使用 Netty userEventTriggered 来调度它的调用,虽然这使得 class 更难使用,但你可以用它做更多的事情它。

例如,如果您的协议支持 ping 消息,则可以使用此 class 来发送那些 ping 消息。这个 class 真的很容易使用,可以在处理程序中使用,如下所示:

public class MyChannelInitializer extends ChannelInitializer<Channel> {
     @Override
     public void initChannel(Channel channel) {
         channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0));
         channel.pipeline().addLast("myHandler", new MyHandler());
     }
 }

 // Handler should handle the IdleStateEvent triggered by IdleStateHandler.
 public class MyHandler extends ChannelHandlerAdapter {
     @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
         if (evt instanceof IdleStateEvent) {
             IdleStateEvent e = (IdleStateEvent) evt;
             if (e.state() == IdleState.READER_IDLE) {
                 ctx.close();
             } else if (e.state() == IdleState.WRITER_IDLE) {
                 ctx.writeAndFlush(new PingMessage());
             }
         }
     }
 }