1

我正在挖掘我的netty程序中的一个错误:我在服务器和客户端之间使用了一个心跳处理程序,当客户端系统重新启动时,服务器端的心跳处理程序会意识到超时然后关闭通道,但有时监听器注册在频道的 CloseFuture 永远不会被通知,这很奇怪。

在挖掘netty 3.5.7源码后,我发现只有通过AbstractChannel.setClosed()来通知Channel的CloseFuture;可能这个方法在Channel关闭时不会执行,见下图:

NioServerSocketPipelineSink:

private static void close(NioServerSocketChannel channel, ChannelFuture future) {
    boolean bound = channel.isBound();
    try {
        if (channel.socket.isOpen()) {
            channel.socket.close();
            Selector selector = channel.selector;
            if (selector != null) {
                selector.wakeup();
            }
        }

        // Make sure the boss thread is not running so that that the future
        // is notified after a new connection cannot be accepted anymore.
        // See NETTY-256 for more information.
        channel.shutdownLock.lock();
        try {
            if (channel.setClosed()) {
                future.setSuccess();
                if (bound) {
                    fireChannelUnbound(channel);
                }
                fireChannelClosed(channel);
            } else {
                future.setSuccess();
            }
        } finally {
            channel.shutdownLock.unlock();
        }
    } catch (Throwable t) {
        future.setFailure(t);
        fireExceptionCaught(channel, t);
    }
}

在某些平台 channel.socket.close() 可能会抛出 IOException,这意味着 channel.setClosed() 可能永远不会执行,因此可能不会通知在 CloseFuture 中注册的侦听器。

这是我的问题:你遇到过这个问题吗?分析对吗?

我发现这是我的心跳处理程序导致问题:永远不会超时,所以永远不要关闭频道,下面是在计时器中运行:

if ((now - lastReadTime > heartbeatTimeout)
                    && (now - lastWriteTime > heartbeatTimeout)) {
                getChannel().close();
                stopHeartbeatTimer();
            }

其中 lastReadTime 和 lastWriteTime 更新如下:

public void writeComplete(ChannelHandlerContext ctx, WriteCompletionEvent e)
        throws Exception {
    lastWriteTime = System.currentTimeMillis();
    super.writeComplete(ctx, e);
}

public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
        throws Exception {
    lastReadTime = System.currentTimeMillis();
    super.messageReceived(ctx, e);
}

远程客户端是Windows xp,当前服务器是Linux,都是jdk1.6。我认为在远程客户端系统重新启动后,writeComplete 仍然在内部调用,虽然没有调用 messageReceived,但在此期间没有抛出 IOExceptoin。

我将重新设计心跳处理程序,在心跳包中附加一个时间戳和一个HEART_BEAT标志,当对端收到数据包时,以相同的时间戳和ACK_HEART_BEAT标志发回数据包,当当前端收到这个ack包时,使用这个更新 lastWriteTime 的时间戳。

4

0 回答 0