git » crow.game » commit 3671fc2

cleanup example ping/pong, import-order fixes

author b0in
2023-12-07 01:11:51 UTC
committer b0in
2023-12-07 01:11:51 UTC
parent a2007102ef150af2081c7bacf18d933327f36762

cleanup example ping/pong, import-order fixes

crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java +2 -1
crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java +9 -6
crow.game.codec.netty/src/main/java/crow/game/codec/netty/NettyWrappedContext.java +4 -2
crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java +6 -4
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/ClientSession.java +67 -0
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/MainClient.java +6 -4
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/PingPongClientInitializer.java +10 -29
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java +2 -1
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java +2 -1
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/RootContext.java +2 -1
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/MainServer.java +6 -4
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/PingPongServerInitializer.java +14 -35
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/ServerSession.java +53 -0
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/utils/Main.java +4 -2
pom.xml +3 -0

diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
index dd473fb..b23e2f0 100644
--- a/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
@@ -1,9 +1,10 @@
 package crow.game.codec;
 
-import io.netty.buffer.ByteBuf;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import io.netty.buffer.ByteBuf;
+
 /**
  * Interface responsible for converting to and from Packet objects to a POJO type
  *
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
index 1742f41..670920a 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
@@ -1,18 +1,21 @@
 package crow.game.codec.netty;
 
-import crow.game.codec.Context;
-import crow.game.codec.Packet;
-import crow.game.codec.PacketHandler;
-import crow.game.codec.PacketRule;
+import java.util.List;
+import java.util.Optional;
+
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageCodec;
 import io.netty.handler.codec.UnsupportedMessageTypeException;
-import java.util.List;
-import java.util.Optional;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.codec.Context;
+import crow.game.codec.Packet;
+import crow.game.codec.PacketHandler;
+import crow.game.codec.PacketRule;
+
 /**
  * The FrameCodec converts to and from {@link ByteBuf} streams into individual {@link
  * crow.game.codec.Packet} objects. The first two bytes of a packet determine which type of packet
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/NettyWrappedContext.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/NettyWrappedContext.java
index ffe6e70..a6075cc 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/NettyWrappedContext.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/NettyWrappedContext.java
@@ -1,9 +1,11 @@
 package crow.game.codec.netty;
 
+import java.util.List;
+
+import io.netty.channel.ChannelHandlerContext;
+
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
-import io.netty.channel.ChannelHandlerContext;
-import java.util.List;
 
 /**
  * Implementation of {@link Context} which wraps an upper-level context and sets the {@link
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
index 80472ad..e8585ce 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
@@ -1,13 +1,15 @@
 package crow.game.codec.netty;
 
-import crow.game.codec.Context;
-import crow.game.codec.Packet;
-import crow.game.codec.PacketHandler;
+import java.util.List;
+
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.MessageToMessageCodec;
-import java.util.List;
+
+import crow.game.codec.Context;
+import crow.game.codec.Packet;
+import crow.game.codec.PacketHandler;
 
 /**
  * Codec responsible for converting to and from {@link crow.game.codec.Packet} objects and the
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/ClientSession.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/ClientSession.java
new file mode 100644
index 0000000..69a4c9c
--- /dev/null
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/ClientSession.java
@@ -0,0 +1,67 @@
+package crow.game.examples.pingpong.client;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import crow.game.examples.pingpong.proto.Ping;
+import crow.game.examples.pingpong.proto.Pong;
+
+/**
+ * This contains the state and netty behavior of the ping pong client.
+ *
+ * <p>We use scheduleAtFixedRate to trigger pings from the client to server as a keepalive. The HA
+ * rathena code performs application-level ping/pong so this is an example for eventual usage in
+ * that module.
+ *
+ * <p>We send 5 pings in an interval and then cancel the recurring task them which should trigger an
+ * idle event on the server, and the server will kill the connection. This helps test a few
+ * assumptions and designs that will go into the main server(s).
+ */
+public class ClientSession extends ChannelInboundHandlerAdapter {
+
+  public static final Logger logger = LoggerFactory.getLogger(ClientSession.class);
+
+  protected int count;
+  protected ScheduledFuture<?> pinger;
+
+  public ClientSession() {
+    this.count = 0;
+  }
+
+  public void sendPing(ChannelHandlerContext ctx) {
+    ctx.writeAndFlush(new Ping());
+  }
+
+  @Override
+  public void channelActive(ChannelHandlerContext ctx) {
+    pinger =
+        ctx.executor()
+            .scheduleAtFixedRate(
+                () -> sendPing(ctx), //
+                10, //
+                10, //
+                TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void channelRead(ChannelHandlerContext ctx, Object msg) {
+    if (msg instanceof Pong) {
+      logger.info("GOT PONG");
+      if (++this.count > 5) {
+        logger.info("GOT 5 PONGS, not sending anymore pings");
+        pinger.cancel(/*???=*/ true);
+      }
+    }
+  }
+
+  @Override
+  public void channelInactive(ChannelHandlerContext ctx) {
+    logger.info("channel done");
+  }
+}
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/MainClient.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/MainClient.java
index d383914..3b9e2c4 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/MainClient.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/MainClient.java
@@ -1,17 +1,19 @@
 package crow.game.examples.pingpong.client;
 
-import crow.game.codec.Context;
-import crow.game.examples.pingpong.proto.Constants;
-import crow.game.examples.pingpong.proto.RootContext;
-import crow.game.examples.pingpong.utils.PrefixThreadFactory;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.nio.NioSocketChannel;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.codec.Context;
+import crow.game.examples.pingpong.proto.Constants;
+import crow.game.examples.pingpong.proto.RootContext;
+import crow.game.examples.pingpong.utils.PrefixThreadFactory;
+
 /**
  * This wraps the client into a runnable that can be run as a main entrypoint OR passed into a
  * Thread.
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/PingPongClientInitializer.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/PingPongClientInitializer.java
index b798dd1..7d54a68 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/PingPongClientInitializer.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/client/PingPongClientInitializer.java
@@ -1,21 +1,20 @@
 package crow.game.examples.pingpong.client;
 
-import crow.game.codec.Context;
-import crow.game.codec.netty.FrameCodec;
-import crow.game.codec.netty.PacketCodec;
-import crow.game.examples.pingpong.proto.Ping;
-import crow.game.examples.pingpong.proto.Pong;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.logging.LoggingHandler;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.codec.Context;
+import crow.game.codec.netty.FrameCodec;
+import crow.game.codec.netty.PacketCodec;
+
 /**
  * Netty clients logic for the ping pong example. it registers the standard {@link FrameCodec} and
- * {@link PacketCodec} then just sends a {@link Ping} message once the connection is established,
- * finally drops the connection once a {@link Pong} is received.
+ * {@link PacketCodec} alongside the standard logging handler and finally {@link ClientSession}
+ * which acts as the lifecycle/stateful object for the session.
  */
 public class PingPongClientInitializer extends ChannelInitializer<SocketChannel> {
 
@@ -31,27 +30,9 @@ public class PingPongClientInitializer extends ChannelInitializer<SocketChannel>
   protected void initChannel(SocketChannel ch) throws Exception {
     logger.info("initializing channel");
     ch.pipeline() //
+        .addLast(new LoggingHandler())
         .addLast(new FrameCodec(context)) //
         .addLast(new PacketCodec(context)) //
-        .addLast(
-            new ChannelInboundHandlerAdapter() {
-              @Override
-              public void channelActive(ChannelHandlerContext ctx) throws Exception {
-                ctx.writeAndFlush(new Ping()).sync();
-              }
-
-              @Override
-              public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
-                if (msg instanceof Pong) {
-                  logger.info("GOT PONG");
-                  ctx.close();
-                }
-              }
-
-              @Override
-              public void channelInactive(ChannelHandlerContext ctx) throws Exception {
-                logger.info("channel done");
-              }
-            });
+        .addLast(new ClientSession());
   }
 }
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
index c921eef..9141c1a 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
@@ -1,9 +1,10 @@
 package crow.game.examples.pingpong.proto;
 
+import io.netty.buffer.ByteBuf;
+
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
 import crow.game.codec.PacketRule;
-import io.netty.buffer.ByteBuf;
 
 /**
  * This is the packet handler for the Ping message. It, ironically, is empty since Ping is a 0-byte
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
index edbaa24..1d67072 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
@@ -1,9 +1,10 @@
 package crow.game.examples.pingpong.proto;
 
+import io.netty.buffer.ByteBuf;
+
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
 import crow.game.codec.PacketRule;
-import io.netty.buffer.ByteBuf;
 
 /**
  * This is the packet handler for the Pong message. It, ironically, is empty since Pong is a 0-byte
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/RootContext.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/RootContext.java
index 74bd4ab..66ff776 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/RootContext.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/RootContext.java
@@ -1,8 +1,9 @@
 package crow.game.examples.pingpong.proto;
 
+import java.util.List;
+
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
-import java.util.List;
 
 /**
  * The root context is the context implementation for the Ping/Pong server and defines the packet
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/MainServer.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/MainServer.java
index c9f4c0b..9627ab0 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/MainServer.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/MainServer.java
@@ -1,18 +1,20 @@
 package crow.game.examples.pingpong.server;
 
-import crow.game.codec.Context;
-import crow.game.examples.pingpong.proto.Constants;
-import crow.game.examples.pingpong.proto.RootContext;
-import crow.game.examples.pingpong.utils.PrefixThreadFactory;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.codec.Context;
+import crow.game.examples.pingpong.proto.Constants;
+import crow.game.examples.pingpong.proto.RootContext;
+import crow.game.examples.pingpong.utils.PrefixThreadFactory;
+
 /**
  * This wraps the server into a runnable that can be run as a main entrypoint OR passed into a
  * Thread.
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/PingPongServerInitializer.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/PingPongServerInitializer.java
index 29f813d..e3beba5 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/PingPongServerInitializer.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/PingPongServerInitializer.java
@@ -1,26 +1,23 @@
 package crow.game.examples.pingpong.server;
 
-import crow.game.codec.Context;
-import crow.game.codec.netty.FrameCodec;
-import crow.game.codec.netty.PacketCodec;
-import crow.game.examples.pingpong.proto.Ping;
-import crow.game.examples.pingpong.proto.Pong;
-import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInitializer;
-import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.channel.socket.SocketChannel;
-import io.netty.handler.codec.DecoderException;
-import io.netty.handler.codec.UnsupportedMessageTypeException;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.timeout.IdleStateHandler;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.codec.Context;
+import crow.game.codec.netty.FrameCodec;
+import crow.game.codec.netty.PacketCodec;
+
 /**
  * Netty server logic for the ping pong example. it registers the standard {@link FrameCodec} and
- * {@link PacketCodec} then just waits for a {@link Ping} object to come from a client.
+ * {@link PacketCodec} as well as the {@link IdleStateHandler}, which lets us track connection idle
+ * events and trigger Pings or drop stale connections.
  *
- * <p>The bottom of the pipeline is a {@link SimpleChannelInboundHandler} with two functions: listen
- * for Ping messages and handle exceptions thrown by the codecs and report and drop the connection
- * as needed.
+ * @see ServerSession
  */
 public class PingPongServerInitializer extends ChannelInitializer<SocketChannel> {
 
@@ -35,28 +32,10 @@ public class PingPongServerInitializer extends ChannelInitializer<SocketChannel>
   @Override
   protected void initChannel(SocketChannel ch) {
     ch.pipeline() //
+        .addLast(new LoggingHandler())
         .addLast(new FrameCodec(context)) //
-        .addLast(new PacketCodec(context)) //
-        .addLast(
-            new SimpleChannelInboundHandler<Ping>() {
-              @Override
-              protected void channelRead0(ChannelHandlerContext channelHandlerContext, Ping ping) {
-                logger.info("GOT PING, RESPONDING");
-                channelHandlerContext.writeAndFlush(new Pong());
-              }
-
-              @Override
-              public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
-                if (cause instanceof DecoderException) {
-                  if (cause.getCause() instanceof UnsupportedMessageTypeException) {
-                    logger.warn("dropping connection due to malformed packet");
-                    ctx.close();
-                    return;
-                  }
-                }
-
-                logger.error("error", cause);
-              }
-            });
+        .addLast(new PacketCodec(context))
+        .addLast(new IdleStateHandler(0, 0, 20)) //
+        .addLast(new ServerSession());
   }
 }
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/ServerSession.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/ServerSession.java
new file mode 100644
index 0000000..b9b401d
--- /dev/null
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/server/ServerSession.java
@@ -0,0 +1,53 @@
+package crow.game.examples.pingpong.server;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.DecoderException;
+import io.netty.handler.codec.UnsupportedMessageTypeException;
+import io.netty.handler.timeout.IdleStateEvent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import crow.game.examples.pingpong.proto.Ping;
+import crow.game.examples.pingpong.proto.Pong;
+
+/**
+ * Acts as the stateful object for tracking the server side of a connection. There is no state (yet)
+ * but it's "stateful" in spirit.
+ *
+ * <p>All this does is wait for ping messages OR wait for idle events.
+ */
+public class ServerSession extends ChannelInboundHandlerAdapter {
+
+  public static final Logger logger = LoggerFactory.getLogger(ServerSession.class);
+
+  @Override
+  public void channelRead(ChannelHandlerContext ctx, Object msg) {
+    if (msg instanceof Ping) {
+      logger.info("GOT PING, RESPONDING");
+      ctx.writeAndFlush(new Pong());
+    }
+  }
+
+  @Override
+  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+    if (evt instanceof IdleStateEvent) {
+      logger.warn("session idle, dropping");
+      ctx.close();
+    }
+  }
+
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+    if (cause instanceof DecoderException) {
+      if (cause.getCause() instanceof UnsupportedMessageTypeException) {
+        logger.warn("dropping connection due to malformed packet");
+        ctx.close();
+        return;
+      }
+    }
+
+    logger.error("error", cause);
+  }
+}
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/utils/Main.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/utils/Main.java
index 90e0ae1..5e12c5b 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/utils/Main.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/utils/Main.java
@@ -1,14 +1,16 @@
 package crow.game.examples.pingpong.utils;
 
-import crow.game.examples.pingpong.client.MainClient;
-import crow.game.examples.pingpong.server.MainServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import crow.game.examples.pingpong.client.MainClient;
+import crow.game.examples.pingpong.server.MainServer;
+
 /** This main runs both client and server as separate threads. */
 public class Main {
 
   public static void main(String[] args) {
+    System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "INFO");
     Logger l = LoggerFactory.getLogger(Main.class);
     l.info("running...");
     Thread thr1 = new Thread(new MainServer(), "server");
diff --git a/pom.xml b/pom.xml
index b2410cc..157f31c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,9 @@
               <include>crow*/src/test/java/**/*.java</include>
             </includes>
             <googleJavaFormat></googleJavaFormat>
+            <importOrder>
+              <order>java|javax,io.netty,org,,crow.game,\#</order>
+            </importOrder>
           </java>
           <markdown>
             <includes>