Java Netty websocket客户端在空闲5分钟后不会从服务器读取新帧

Java Netty websocket客户端在空闲5分钟后不会从服务器读取新帧,java,websocket,netty,Java,Websocket,Netty,我正在使用Netty服务器端和客户端来建立和控制websocket连接。我在服务器端有一个,当通道读取器、写入器或两者空闲一段时间时,它将发送一个用户事件。我这样做是为了在空闲5分钟后触发writer idle事件,在空闲6分钟后触发reader idle事件。在writer idle事件期间,服务器将向客户端发送一个ping帧,一旦从客户端接收到pong帧,该ping帧将重置writer空闲时间和读取器空闲时间 问题是netty客户端在空闲5分钟后似乎没有读取任何新帧。我在客户端的通道上做了一

我正在使用Netty服务器端和客户端来建立和控制websocket连接。我在服务器端有一个,当通道读取器、写入器或两者空闲一段时间时,它将发送一个用户事件。我这样做是为了在空闲5分钟后触发writer idle事件,在空闲6分钟后触发reader idle事件。在writer idle事件期间,服务器将向客户端发送一个ping帧,一旦从客户端接收到pong帧,该ping帧将重置writer空闲时间和读取器空闲时间

问题是netty客户端在空闲5分钟后似乎没有读取任何新帧。我在客户端的通道上做了一些状态检查,看看它在5分钟的空闲时间后是否可写、已注册、已打开和处于活动状态,所有状态都为true,但没有读取新帧。为了解决这个问题,我只是简单地将服务器端的IdleStateHandler时间改为3分钟,而不是5分钟,这样客户端将在空闲5分钟之前收到一个ping帧并用pong帧进行响应

但这并不能解决根本问题。我希望了解并能够控制客户的读卡器何时空闲,并能够防止将来出现丢失或未读数据的问题。查看下面的代码,如果没有从客户端接收到pong或heartbeat帧,空闲事件处理程序将关闭通道连接,但由于客户端不读取新帧,因此它永远不会获得关闭帧,因此服务器认为客户端未连接,而客户端认为它已连接,这显然会导致问题。是否有任何方法可以使用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);
    }
}
私有类ConnectServerInitializer扩展了ChannelInitializer{
私有最终IdleEventHandler IdleEventHandler=新IdleEventHandler();
专用最终SslContext sslCtx;
专用ConnectServerInitializer(SslContext sslCtx){
this.sslCtx=sslCtx;
}
@凌驾
public void initChannel(SocketChannel ch)引发异常{
ChannelPipeline=通道管道();
如果(sslCtx!=null){
pipeline.addLast(sslCtx.newHandler(ch.alloc());
}
addLast(新的HttpServerCodec());
addLast(新的HttpObjectAggregator(65536));
pipeline.addLast(idleEventHandler.newStateHandler());
pipeline.addLast(idleEventHandler);
addLast(getHandler());
}
}
@可分享
私有类IdleEventHandler扩展了ChannelDuplexHandler{
私有静态最终字符串HEARTBEAT_CONTENT=“--HEARTBEAT--”;
私有静态最终int读取器\u IDLE\u TIMEOUT=200;//比writer多20秒以允许pong响应
私有静态final int WRITER_IDLE_TIMEOUT=180;//注意:netty客户端在空闲5分钟后不会读取帧
//当客户端不支持ping/pong帧时,这是一种回退
私有最终属性key USE_HEARTBEAT=AttributeKey.valueOf(“USE HEARTBEAT”);
@凌驾
public void userEventTriggered(ChannelHandlerContext ctx,对象事件)引发异常{
if(IdleStateEvent的事件实例){
IDLESTATEVENT e=(IDLESTATEVENT)事件;
布尔useHeartbeat=ctx.attr(USE_HEARTBEAT.get();
if(e.state()==IdleState.READER\u IDLE){
如果(UseCheartbeat==null){
logger.info(“客户端”+ctx.channel()+“未响应ping帧。正在发送心跳消息…”);
ctx.attr(使用心跳).set(true);
发送心跳(ctx);
}否则{
logger.warn(“客户端”+ctx.channel()+“空闲时间太长。正在关闭websocket连接…”);
ctx.close();
}
}else if(e.state()==idlstate.WRITER_IDLE | | e.state()==idlstate.ALL_IDLE){
如果(UseCheartbeat==null | |!UseCheartbeat){
ByteBuf ping=unmooled.wrappedBuffer(HEARTBEAT_CONTENT.getBytes());
ctx.writeAndFlush(新的PingWebSocketFrame(ping));
}否则{
发送心跳(ctx);
}
}
}
}
私有void sendHeartbeat(ChannelHandlerContext ctx){
字符串json=getHandler().getMessenger().serialize(新的HeartbeatMessage(心跳内容));
writeAndFlush(新的TextWebSocketFrame(json));
}
私有IdleStateHandler newStateHandler(){
返回新的IdleStateHandler(读卡器空闲超时、写卡器空闲超时、写卡器空闲超时);
}
}

您的问题与防火墙超时有关。某些防火墙的超时时间接近5分钟,如果超过此超时时间,则会自动断开连接。因此,客户端和服务器需要有一些读取超时来检查这个事实,而服务器、客户端或两者都有一些ping消息。当您在IPv6上运行协议时,防火墙问题会减少,因为大多数IPv6防火墙主要是无状态的,并且通常不会更改连接端口,因此来自客户端的数据包会再次激活防火墙中的条目


当你有5分钟超时的时候,你应该考虑从WebSooCK中的额外负载是否可以与每1分钟一个简单的轮询HTTP循环的负载进行比较,因为这会在服务器上造成更少的内存紧张。

你是否使用本地主机IP地址测试应用程序,还是通过互联网?@Ferrybig它在本地主机上工作得很好,但一旦我部署到我的Web服务器并通过公共IP进行测试,它就