我正在挖掘我的netty程序中的一个错误:我在服务器和客户端之间使用了一个心跳处理程序,当客户端系统重新启动时,服务器端的心跳处理程序将知道超时,然后关闭通道,但有时候在Channel的CloseFuture注册的监听器永远不会被通知,这很奇怪。
在挖掘netty 3.5.7源代码之后,我发现通道的CloseFuture被通知的唯一方法是通过AbstractChannel.setClosed();可能在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标志的数据包,使用此时间戳更新lastWriteTime。