我在java应用程序中使用netty.io(4.0.4)来实现TCP客户端与外部硬件驱动程序进行通信。这种硬件的要求之一是,客户端每30秒发送一次KEEP_ALIVE(心跳)消息,然而硬件不响应这种热节拍。 我的问题是,当连接突然断开时(例如:网络电缆拔出),客户端完全没有意识到这一点,并且在获得操作超时异常之前继续发送KEEP_ALIVE消息更长时间(大约5-10分钟)。 换句话说,从客户端来看,无法判断它是否仍然连接。
下面是我的引导程序设置的片段,如果它有帮助
// bootstrap setup
bootstrap = new Bootstrap().group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.remoteAddress(ip, port)
.handler(tcpChannelInitializer);
// part of the pipeline responsible for keep alive messages
pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS));
pipeline.addLast("keepAliveHandler", keepAliveMessageHandler);
我希望由于客户端发送保持活动消息,并且在另一端没有收到这些消息,丢失的确认应该更早地指示连接中的问题?
修改
KeepAliveMessageHandler的代码
public class KeepAliveMessageHandler extends ChannelDuplexHandler
{
private static final Logger LOGGER = getLogger(KeepAliveMessageHandler.class);
private static final String KEEP_ALIVE_MESSAGE = "";
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception
{
if (!(evt instanceof IdleStateEvent)) {
return;
}
IdleStateEvent e = (IdleStateEvent) evt;
Channel channel = ctx.channel();
if (e.state() == IdleState.ALL_IDLE) {
LOGGER.info("Sending KEEP_ALIVE_MESSAGE");
channel.writeAndFlush(KEEP_ALIVE_MESSAGE);
}
}
}
编辑2
我厌倦了明确确保使用下面的代码传递保持活动消息
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception
{
if (!(evt instanceof IdleStateEvent)) {
return;
}
IdleStateEvent e = (IdleStateEvent) evt;
Channel channel = ctx.channel();
if (e.state() == IdleState.ALL_IDLE) {
LOGGER.info("Sending KEEP_ALIVE_MESSAGE");
channel.writeAndFlush(KEEP_ALIVE_MESSAGE).addListener(future -> {
if (!future.isSuccess()) {
LOGGER.error("KEEP_ALIVE message write error");
channel.close();
}
});
}
}
这也行不通。 :(根据this answer这种行为是有道理的,但我仍然希望有一些方法可以弄清楚写作是否是“真正的”成功。(硬件确认听不到)
答案 0 :(得分:0)
您已启用TCP Keepalive
.option(ChannelOption.SO_KEEPALIVE, true)
但在您的代码中,我无法看到任何确保以30秒的速率发送Keepalive的内容。
如果由于TCP Keepalive超时导致连接终止,而另一个主机最终发送旧连接的数据包,则终止连接的主机将发送一个数据包,其中RST标志设置为发信号通知另一个主机旧连接不再有效。这将强制其他主机终止其连接的结束,以便建立新的连接。
通常在空闲TCP连接上每隔45或60秒发送一次TCP Keepalive,并在错过3次序列ACK后断开连接。这取决于主机,例如默认情况下,Windows PC在7200000ms(2小时)后发送第一个TCP Keepalive数据包,然后以1000ms间隔发送5个Keepalive,如果对任何Keepalive数据包没有响应,则丢弃连接。
(取自http://ltxfaq.custhelp.com/app/answers/detail/a_id/1512/~/tcp-keepalives-explained_
我现在明白了
pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS));
pipeline.addLast("keepAliveHandler", keepAliveMessageHandler);
在相互不活动的情况下,每30秒会触发一次空闲事件,keepAliveMessageHandler
会在这种情况下发送一个数据包以移除一侧。
不幸的是
ChannelFuture future = channel.writeAndFlush(KEEP_ALIVE_MESSAGE);
将写入OS缓冲区时,
被视为成功。
在你的条件下,你似乎只有2个选择:
发送一个会有外部响应的命令
设备(不会造成破坏的东西)
但我认为在你的情况下这是不可能的。
修改底层TCP驱动程序设置
TCP keepalive的默认操作系统设置更多是关于节省系统资源以支持大量应用程序和连接。如果您有专用系统,则可以设置更积极的TCP检查配置。
以下是如何调整linux内核的链接:http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
该解决方案应该在普通安装以及VM和Docker容器中工作。
有关该主题的一般信息:https://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html