From 0bbe249cee778eac72f5825a02d7085b3139a8ab Mon Sep 17 00:00:00 2001 From: Dank074 Date: Tue, 9 Feb 2021 01:35:43 -0600 Subject: [PATCH] fixed pinging to send every 30 seconds, regardless of idle state --- .../networking/gameserver/GameServer.java | 5 +- .../handlers/IdleTimeoutHandler.java | 139 ++++++++++++++++-- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/eu/habbo/networking/gameserver/GameServer.java b/src/main/java/com/eu/habbo/networking/gameserver/GameServer.java index 3aa20998..98647cc7 100644 --- a/src/main/java/com/eu/habbo/networking/gameserver/GameServer.java +++ b/src/main/java/com/eu/habbo/networking/gameserver/GameServer.java @@ -32,9 +32,6 @@ public class GameServer extends Server { public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("logger", new LoggingHandler()); - ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 30, 0)); - ch.pipeline().addAfter("idleStateHandler", "idleEventHandler", new IdleTimeoutHandler()); - // Decoders. ch.pipeline().addLast(new GamePolicyDecoder()); ch.pipeline().addLast(new GameByteFrameDecoder()); @@ -43,7 +40,7 @@ public class GameServer extends Server { if (PacketManager.DEBUG_SHOW_PACKETS) { ch.pipeline().addLast(new GameClientMessageLogger()); } - + ch.pipeline().addLast("idleEventHandler", new IdleTimeoutHandler(30, 60)); ch.pipeline().addLast(new GameMessageRateLimit()); ch.pipeline().addLast(new GameMessageHandler()); diff --git a/src/main/java/com/eu/habbo/networking/gameserver/handlers/IdleTimeoutHandler.java b/src/main/java/com/eu/habbo/networking/gameserver/handlers/IdleTimeoutHandler.java index 46bb4c14..26c55666 100644 --- a/src/main/java/com/eu/habbo/networking/gameserver/handlers/IdleTimeoutHandler.java +++ b/src/main/java/com/eu/habbo/networking/gameserver/handlers/IdleTimeoutHandler.java @@ -1,29 +1,136 @@ package com.eu.habbo.networking.gameserver.handlers; import com.eu.habbo.habbohotel.gameclients.GameClient; +import com.eu.habbo.messages.ClientMessage; +import com.eu.habbo.messages.incoming.Incoming; import com.eu.habbo.messages.outgoing.handshake.PingComposer; import com.eu.habbo.networking.gameserver.GameServerAttributes; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.timeout.IdleState; -import io.netty.handler.timeout.IdleStateEvent; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class IdleTimeoutHandler extends ChannelDuplexHandler { - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - if (evt instanceof IdleStateEvent) { - IdleStateEvent e = (IdleStateEvent) evt; - if (e.state() == IdleState.READER_IDLE) { - ctx.close(); - } else if (e.state() == IdleState.WRITER_IDLE) { - GameClient client = ctx.channel().attr(GameServerAttributes.CLIENT).get(); - if (client != null) { - client.sendResponse(new PingComposer()); - } - } - } else { - super.userEventTriggered(ctx, evt); + private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); + + private final long pingScheduleNanos; + private final long pongTimeoutNanos; + + volatile ScheduledFuture pingScheduleFuture; + volatile long lastPongTime;// in nanoseconds + + private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed + + public IdleTimeoutHandler(int pingScheduleSeconds, int pongTimeoutSeconds) { + this.pingScheduleNanos = Math.max(MIN_TIMEOUT_NANOS, TimeUnit.SECONDS.toNanos(pingScheduleSeconds)); + this.pongTimeoutNanos = Math.max(MIN_TIMEOUT_NANOS, TimeUnit.SECONDS.toNanos(pongTimeoutSeconds)); + } + + private void initialize(ChannelHandlerContext ctx) { + // Avoid the case where destroy() is called before scheduling timeouts. + // See: https://github.com/netty/netty/issues/143 + switch (state) { + case 1: + case 2: + return; + } + + state = 1; + + lastPongTime = System.nanoTime(); + if (pingScheduleNanos > 0) { + pingScheduleFuture = ctx.executor().schedule(new PingScheduledTask(ctx), pingScheduleNanos, TimeUnit.NANOSECONDS); } } + private void destroy() { + state = 2; + + if (pingScheduleFuture != null) { + pingScheduleFuture.cancel(false); + pingScheduleFuture = null; + } + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + if (ctx.channel().isActive() && ctx.channel().isRegistered()) { + // channelActvie() event has been fired already, which means this.channelActive() will + // not be invoked. We have to initialize here instead. + initialize(ctx); + } else { + // channelActive() event has not been fired yet. this.channelActive() will be invoked + // and initialization will occur there. + } + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + destroy(); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + // Initialize early if channel is active already. + if (ctx.channel().isActive()) { + initialize(ctx); + } + super.channelRegistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // This method will be invoked only if this handler was added + // before channelActive() event is fired. If a user adds this handler + // after the channelActive() event, initialize() will be called by beforeAdd(). + initialize(ctx); + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + destroy(); + super.channelInactive(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + // check if its a pong message + if(msg instanceof ClientMessage) { + ClientMessage packet = (ClientMessage) msg; + if(packet.getMessageId() == Incoming.PongEvent) { + this.lastPongTime = System.nanoTime(); + } + } + super.channelRead(ctx, msg); + } + + private final class PingScheduledTask implements Runnable { + private final ChannelHandlerContext ctx; + + public PingScheduledTask(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void run() { + if (!ctx.channel().isOpen()) { + return; + } + + long currentTime = System.nanoTime(); + if(currentTime - lastPongTime > pongTimeoutNanos) { + ctx.close();// add a promise here ? + return; + } + + GameClient client = ctx.channel().attr(GameServerAttributes.CLIENT).get(); + if (client != null) { + client.sendResponse(new PingComposer()); + } + + pingScheduleFuture = ctx.executor().schedule(this, pingScheduleNanos, TimeUnit.NANOSECONDS); + } + } }