| author | b0in
<b0in@proton.me> 2023-12-07 01:11:51 UTC |
| committer | b0in
<b0in@proton.me> 2023-12-07 01:11:51 UTC |
| parent | a2007102ef150af2081c7bacf18d933327f36762 |
| 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>