【问题标题】:netty client takes very long before broken network is detectednetty 客户端需要很长时间才能检测到断网
【发布时间】:2018-03-18 18:07:15
【问题描述】:

我在 java 应用程序中使用 netty.io (4.0.4) 来实现 TCP 客户端以与外部硬件驱动程序通信。该硬件的要求之一是,客户端每 30 秒发送一次 KEEP_ALIVE(心跳)消息,但是硬件不响应此心跳。 我的问题是,当连接突然断开时(例如:网线被拔出),客户端完全没有意识到这一点,并在收到操作超时异常之前继续发送 KEEP_ALIVE 消息更长时间(大约 5-10 分钟)。 换句话说,从客户端,没有办法判断它是否仍然连接。

如果有帮助,下面是我的引导设置的 sn-p

// 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,这种行为是有道理的,但我仍然希望有一些方法可以确定写入是否“真正”成功。(让硬件确认心跳是不可能的)

【问题讨论】:

  • 或许看看这里的答案? stackoverflow.com/questions/21358800/…
  • 感谢该链接,我在提出问题之前查看了该链接,我对该解决方案的问题是:由于网线已拔下,无法正常关闭通道 b.实现 ReadTimeoutHandler 将不起作用,因为硬件并没有说太多,所以这会经常被触发:/(我在问题中谈论的 ack 是 TCP 层 ack 而不是应用程序级别)。有道理?也许我想要的 TCP 甚至都无法实现,这就是问题的一部分。
  • 我希望您在几分钟后收到“连接重置”或“软件导致连接中止”。您确定在发送心跳时正确检测到发送错误吗?
  • @EJP 也许我没有正确检测到错误,我所做的只是像这样发送心跳.. IdleStateEvent e = (IdleStateEvent) evt;频道频道 = ctx.channel(); if (e.state() == IdleState.ALL_IDLE) { LOGGER.info("发送 KEEP_ALIVE_MESSAGE");通道.writeAndFlush(KEEP_ALIVE_MESSAGE); }

标签: java tcp netty tcp-ip plc


【解决方案1】:

您已启用 TCP Keepalive

.option(ChannelOption.SO_KEEPALIVE, true)

但在您的代码中,我看不到任何确保以 30 秒的速率发送 keepalive 的部分。

如果连接由于 TCP Keepalive 超时而终止,并且另一台主机最终为旧连接发送数据包,则终止连接的主机将发送一个设置了 RST 标志的数据包以向另一台主机发送信号旧连接不再处于活动状态。这将强制另一台主机终止连接,以便建立新的连接。

通常在空闲 TCP 连接上每 45 或 60 秒发送一次 TCP Keepalive,并在丢失 3 个连续 ACK 后断开连接。这因主机而异,例如默认情况下,Windows PC 会在 7200000 毫秒(2 小时)后发送第一个 TCP Keepalive 数据包,然后以 1000 毫秒的间隔发送 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);

当它被写入操作系统缓冲区时被认为是成功的。

在您的条件下,您似乎只有 2 个选项:

  1. 发送一个命令,该命令将从外部得到一些响应 设备(不会造成干扰的东西)
    但我认为在你的情况下这是不可能的。

  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

【讨论】:

  • 我确实看过 SO 帖子,就像我在后续评论中所说的那样,该解决方案不起作用(我试过了,如果没有读取它会抛出异常,确实如此不一定意味着连接已死,这不是我想要的)。我还在原始问题中添加了我的 KeepAliveHandler 代码。非常感谢您的帮助
  • 现在情况完全不同了。我有一个更新给你。如果没有帮助,请添加有关您的发送超时、重试次数以及您的 KEEP_ALIVE_MESSAGE 究竟是什么的信息。
  • 所以我确实尝试处理 writeAndFlush() 调用后返回的 ChannelFuture,如下所示: channel.writeAndFlush(KEEP_ALIVE_MESSAGE).addListener(future -> { if (!future.isSuccess() ) { LOGGER.error("KEEP_ALIVE 消息写入错误"); channel.close(); } });但这不起作用, if 块没有执行!我读到 netty 在数据写入 IO 缓冲区时表示成功,而不是在另一端接收到数据时。
  • 你是对的,当它被写入操作系统缓冲区时,操作被认为是成功的。不幸的是,除了 1) 修改底层操作系统的 keepalive 设置 2) 发送一个命令,该命令将有来自外部设备的一些响应(不会导致中断的东西)
  • 我的最新更新。如果你考虑过这个选项,我已经发布了一些关于重新配置 linux 内核 TCP 设置的信息。
猜你喜欢
  • 1970-01-01
  • 2019-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-16
相关资源
最近更新 更多