本文介绍了闲置5分钟后,Netty WebSocket客户端无法从服务器读取新帧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在服务器端和客户端都使用Netty来建立和控制Websocket连接.我在服务器端有一个 IdleStateHandler 发送通道读取器,写入器或两者闲置一定时间后的用户事件.我这样做的目的是在闲置5分钟后触发写入器闲置事件,并在闲置6分钟后触发读取器闲置事件.在写入器空闲事件期间,服务器将向客户端发送一个ping帧,一旦从客户端接收到pong帧,该ping帧将重置写入器空闲时间以及读取器空闲时间.

问题在于,闲置5分钟后,netty客户端似乎未读取任何新帧.我在客户端的通道上进行了一些状态检查,以查看在5分钟的空闲时间后该通道是否可写,已注册,处于打开状态并且处于活动状态,并且所有状态均为true,但是未读取新帧.为了解决该问题,我只是将服务器端的IdleStateHandler时间更改为3分钟而不是5分钟,以便客户端在空闲5分钟之前会收到ping帧并以pong帧进行响应.

但这不能解决潜在的问题.我想了解并能够控制客户端的阅读器何时闲置,并能够防止丢失或未读数据的将来出现问题.查看下面的代码,如果未从客户端接收到pong或心跳帧,则空闲事件处理程序将关闭通道连接,但是由于客户端未读取新帧,因此它永远不会获得关闭帧,因此服务器认为客户端未连接,并且客户端认为它已连接,这显然会导致问题.有什么方法可以使用Netty在客户端方面获得更多控制权,以解决这一神奇的5分钟超时问题吗?我在文档或源中找不到关于此的任何内容.

这是服务器中相关的空闲事件处理代码:

private class ConnectServerInitializer extends ChannelInitializer<SocketChannel> {

    private final IdleEventHandler idleEventHandler = new IdleEventHandler();
    private final SslContext sslCtx;

    private ConnectServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(idleEventHandler.newStateHandler());
        pipeline.addLast(idleEventHandler);
        pipeline.addLast(getHandler());
    }

}

@Sharable
private class IdleEventHandler extends ChannelDuplexHandler {

    private static final String HEARTBEAT_CONTENT = "--heartbeat--";
    private static final int READER_IDLE_TIMEOUT = 200; // 20 seconds more that writer to allow for pong response
    private static final int WRITER_IDLE_TIMEOUT = 180; // NOTE: netty clients will not read frames after 5 minutes of being idle
    // This is a fallback for when clients do not support ping/pong frames
    private final AttributeKey<Boolean> USE_HEARTBEAT = AttributeKey.valueOf("use-heartbeat");

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception {
        if (event instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) event;
            Boolean useHeartbeat = ctx.attr(USE_HEARTBEAT).get();
            if (e.state() == IdleState.READER_IDLE) {
                if (useHeartbeat == null) {
                    logger.info("Client " + ctx.channel() + " has not responded to ping frame. Sending heartbeat message...");
                    ctx.attr(USE_HEARTBEAT).set(true);
                    sendHeartbeat(ctx);
                } else {
                    logger.warn("Client " + ctx.channel() + " has been idle for too long. Closing websocket connection...");
                    ctx.close();
                }
            } else if (e.state() == IdleState.WRITER_IDLE || e.state() == IdleState.ALL_IDLE) {
                if (useHeartbeat == null || !useHeartbeat) {
                    ByteBuf ping = Unpooled.wrappedBuffer(HEARTBEAT_CONTENT.getBytes());
                    ctx.writeAndFlush(new PingWebSocketFrame(ping));
                } else {
                    sendHeartbeat(ctx);
                }
            }
        }
    }

    private void sendHeartbeat(ChannelHandlerContext ctx) {
        String json = getHandler().getMessenger().serialize(new HeartbeatMessage(HEARTBEAT_CONTENT));
        ctx.writeAndFlush(new TextWebSocketFrame(json));
    }

    private IdleStateHandler newStateHandler() {
        return new IdleStateHandler(READER_IDLE_TIMEOUT, WRITER_IDLE_TIMEOUT, WRITER_IDLE_TIMEOUT);
    }
}
解决方案

您的问题与防火墙超时有关.某些防火墙的超时时间接近5分钟,如果超过此超时时间,连接将被静默删除.因此,bot客户端和服务器需要有一些读取超时以检查这一事实,并且服务器,客户端或两者都有某种ping消息.当您在IPv6上运行协议时,防火墙问题将较少,因为大多数IPv6防火墙主要是无状态的,并且通常不会更改连接的端口,因此来自客户端的数据包会重新激活防火墙中的条目.

如果您有很多5分钟的超时时间,则应考虑是否可以将websocket的额外负载与每1分钟一次的简单轮询http循环的负载进行比较,因为这会减少服务器上的内存紧张. /p>

I am using Netty both server side and client side to establish and control a websocket connection. I have in the server side an IdleStateHandler which will send a user event for when the channel reader, writer, or both have been idle for a certain period of time. I had it so that the writer idle event would be triggered after 5 minutes of being idle, and the reader idle event would be triggered after 6 minutes of being idle. During the writer idle event, the server would send a ping frame to the client which would reset the writer idle time and as well as the reader idle time once the pong frame is received from the client.

The problem is that the netty client seems to not read any new frames after 5 minutes of being idle. I did some status checks on the channel in the client to see if it was writable, registered, open, and active after that 5 minute idle period, and all states were true but new frames were not being read. To solve the issue, I simply just changed the IdleStateHandler times in the server side to 3 minutes rather than 5 so that the client would receive a ping frame and respond with a pong frame before the 5 minutes of being idle.

But this does not solve the underlying problem. I want to understand and be able to control when the client's reader goes idle and be able to prevent future problems with lost or unread data. Looking at the code below, the idle event handler will close the channel connection if no pong or heartbeat frame was received from the client, but since the client does not read new frames, it never gets the close frame, and so the server thinks the client is not connected, and the client thinks it is connected which obviously causes problems. Is there any way to gain more control over this magical 5 minute timeout in the client side using Netty? I could not find anything about this in the documentation or source.

Here is the related idle event handling code in the server:

private class ConnectServerInitializer extends ChannelInitializer<SocketChannel> {

    private final IdleEventHandler idleEventHandler = new IdleEventHandler();
    private final SslContext sslCtx;

    private ConnectServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(idleEventHandler.newStateHandler());
        pipeline.addLast(idleEventHandler);
        pipeline.addLast(getHandler());
    }

}

@Sharable
private class IdleEventHandler extends ChannelDuplexHandler {

    private static final String HEARTBEAT_CONTENT = "--heartbeat--";
    private static final int READER_IDLE_TIMEOUT = 200; // 20 seconds more that writer to allow for pong response
    private static final int WRITER_IDLE_TIMEOUT = 180; // NOTE: netty clients will not read frames after 5 minutes of being idle
    // This is a fallback for when clients do not support ping/pong frames
    private final AttributeKey<Boolean> USE_HEARTBEAT = AttributeKey.valueOf("use-heartbeat");

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception {
        if (event instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) event;
            Boolean useHeartbeat = ctx.attr(USE_HEARTBEAT).get();
            if (e.state() == IdleState.READER_IDLE) {
                if (useHeartbeat == null) {
                    logger.info("Client " + ctx.channel() + " has not responded to ping frame. Sending heartbeat message...");
                    ctx.attr(USE_HEARTBEAT).set(true);
                    sendHeartbeat(ctx);
                } else {
                    logger.warn("Client " + ctx.channel() + " has been idle for too long. Closing websocket connection...");
                    ctx.close();
                }
            } else if (e.state() == IdleState.WRITER_IDLE || e.state() == IdleState.ALL_IDLE) {
                if (useHeartbeat == null || !useHeartbeat) {
                    ByteBuf ping = Unpooled.wrappedBuffer(HEARTBEAT_CONTENT.getBytes());
                    ctx.writeAndFlush(new PingWebSocketFrame(ping));
                } else {
                    sendHeartbeat(ctx);
                }
            }
        }
    }

    private void sendHeartbeat(ChannelHandlerContext ctx) {
        String json = getHandler().getMessenger().serialize(new HeartbeatMessage(HEARTBEAT_CONTENT));
        ctx.writeAndFlush(new TextWebSocketFrame(json));
    }

    private IdleStateHandler newStateHandler() {
        return new IdleStateHandler(READER_IDLE_TIMEOUT, WRITER_IDLE_TIMEOUT, WRITER_IDLE_TIMEOUT);
    }
}
解决方案

Your problem is related to the time out of your firewall. Some firewall have a timeout near 5 minutes, and if this timeout is exceeded, the connection is silently dropped. Because of this, bot the client and server need to have some some of read timeout out to check this fact, and either the server, client or both have some sort of ping messages. The firewall problem will be less when you run your protocol over IPv6 as most IPv6 firewalls are mainly stateless and usually don't change the port of the connection, so a packet from the client reactivates the entry in firewall again.

When you have many moments of 5 minute timeouts, you should consider if the extra load from the websockets can be compared to the load of a simple polling http loop every 1 minute, as this creates less memory strain on the server.

这篇关于闲置5分钟后,Netty WebSocket客户端无法从服务器读取新帧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-14 16:00