我在服务器端和客户端都使用 Netty 来建立和控制 websocket 连接。我在服务器端有一个空闲状态处理程序 http://netty.io/4.1/api/io/netty/handler/timeout/IdleStateHandler.html当通道读取器、写入器或两者闲置一段时间后,它将发送用户事件。我设置了写入器空闲事件将在空闲 5 分钟后触发,读取器空闲事件将在空闲 6 分钟后触发。在写入器空闲事件期间,服务器将向客户端发送 ping 帧,一旦从客户端接收到 pong 帧,这将重置写入器空闲时间以及读取器空闲时间。
问题是,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);
}
}
您的问题与防火墙超时有关。某些防火墙的超时时间接近 5 分钟,如果超过此超时,连接将被静默断开。因此,客户端和服务器需要有一些读取超时来检查这一事实,并且服务器、客户端或两者都有某种 ping 消息。当您在 IPv6 上运行协议时,防火墙问题会减少,因为大多数 IPv6 防火墙主要是无状态的,通常不会更改连接的端口,因此来自客户端的数据包会再次重新激活防火墙中的条目。
当您多次出现 5 分钟超时时,您应该考虑是否可以将来自 Websocket 的额外负载与每 1 分钟一次简单轮询 http 循环的负载进行比较,因为这会减少服务器上的内存压力。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)