【问题标题】:Netty: channel.write hangs on disconnectNetty:channel.write 在断开连接时挂起
【发布时间】:2013-09-25 16:42:04
【问题描述】:

我有以下情况:

这样打开一个新的通道连接:

    ClientBootstrap bootstrap = new ClientBootstrap(
             new OioClientSocketChannelFactory(Executors.newCachedThreadPool()));

    icapClientChannelPipeline = new ICAPClientChannelPipeline();           
    bootstrap.setPipelineFactory(icapClientChannelPipeline);
    ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
    channel = future.awaitUninterruptibly().getChannel();

这按预期工作。

通过以下方式将内容写入通道:

channel.write(chunk)

当与服务器的连接仍然存在时,这也可以按预期工作。但是如果服务器宕机(机器离线),调用就会挂起并且不会返回。

我通过在channel.write(chunk) 之前和之后添加日志语句来确认这一点。当连接断开时,只显示之前的日志语句。

  1. 这是什么原因造成的?我认为这些调用都是异步的并立即返回?我也试过 NioClientSocketChannelFactory,同样的行为。

  2. 我尝试使用channel.getCloseFuture(),但从未调用过监听器,我尝试在使用channel.isOpen()channel.isConnected()channel.isWritable() 编写之前检查频道,它们总是正确的......

  3. 如何解决这个问题?没有抛出异常,也没有真正发生任何事情......像this onethis one 这样的一些问题表明,如果没有心跳,就无法检测到通道断开连接。但是我无法实现心跳,因为我无法更改服务器端。

环境:Netty 3、JDK 1.7

【问题讨论】:

    标签: java exception-handling netty disconnect


    【解决方案1】:

    好的,我上周自己解决了这个问题,所以我会添加完整的答案。

    我在 3. 中错了,因为我认为我必须同时更改客户端和服务器端才能获得心跳。如in this question 所述,您可以为此目的使用IdleStateAwareHandler。我是这样实现的:

    IdleStateAwareHandler:

    public class IdleStateAwareHandler extends IdleStateAwareChannelHandler {
    
        @Override
        public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) {
            if (e.getState() == IdleState.READER_IDLE) {
                e.getChannel().write("heartbeat-reader_idle");
            }
            else if (e.getState() == IdleState.WRITER_IDLE) {
                Logger.getLogger(IdleStateAwareHandler.class.getName()).log(
                        Level.WARNING, "WriteIdle detected, closing channel");
                e.getChannel().close();
                e.getChannel().write("heartbeat-writer_idle");
            }
            else if (e.getState() == IdleState.ALL_IDLE) {
                e.getChannel().write("heartbeat-all_idle");
            }
        }
    }
    

    管道:

    public class ICAPClientChannelPipeline implements ICAPClientPipeline {
    
            ICAPClientHandler icapClientHandler;
            ChannelPipeline pipeline;
    
            public ICAPClientChannelPipeline(){
                icapClientHandler = new ICAPClientHandler();
            pipeline = pipeline(); 
                pipeline.addLast("idleStateHandler", new IdleStateHandler(new HashedWheelTimer(10, TimeUnit.MILLISECONDS), 5, 5, 5));
                pipeline.addLast("idleStateAwareHandler", new IdleStateAwareHandler());
                pipeline.addLast("encoder",new IcapRequestEncoder());
                pipeline.addLast("chunkSeparator",new IcapChunkSeparator(1024*4));
                pipeline.addLast("decoder",new IcapResponseDecoder());
                pipeline.addLast("chunkAggregator",new IcapChunkAggregator(1024*4));
                pipeline.addLast("handler", icapClientHandler);            
            }
    
            @Override
        public ChannelPipeline getPipeline() throws Exception {
                return pipeline;
        }                     
    }
    

    这会在 5 秒后检测通道上的任何读取或写入空闲状态。 如您所见,它有点特定于 ICAP,但这对问题无关紧要。

    要对空闲事件做出反应,我需要以下监听器:

    channel.getCloseFuture().addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
                doSomething();
        }
    });
    

    【讨论】: