我正在挖掘我的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 的时间戳。