Netty Source Analysis 3: Create a simple Netty service and test it

Posted by 1josh13 on Sat, 05 Mar 2022 06:14:11 +0100

Create and start a Netty server

How do I create and start a Netty server? Start a netty server and listen on port 8888 with the following code.

public class MyNettyServer {
    private static Logger logger = LogManager.getLogger(MyNettyServer.class);

    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            new ServerBootstrap()
                    .group(boss, worker)//This method returns ServerBootstrap
                    .option(ChannelOption.SO_BACKLOG, 128)//This method returns ServerBootstrap
                    .channel(NioServerSocketChannel.class)//This method returns ServerBootstrap
                    .handler(new MyServerChannelDuplexHandler())//This method returns ServerBootstrap
                    .childHandler(new MyChannelInitializer("Client"))//This method returns ServerBootstrap
                    .bind(8888)//Return to ChannelFuture
                    .sync()//Return to ChannelFuture
                    .addListener(future -> logger.info("Service started successfully:" + future.isSuccess()))//Return to ChannelFuture
                    .channel()//Return to Channel
                    .closeFuture()//Return to ChannelFuture
                    .sync()//Return to ChannelFuture
                    .addListener(future -> logger.info("Port closed,If there is an exception, the cause of the exception is:"+future.cause()));//Return to ChannelFuture
        } finally {
        	//Elegant shutdown of boss and worker
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}
  • NioEventLoopGroup boss: Equivalent to a circular listening port operation, when a client initiates a connection, it gets a socket operation corresponding to the client for communication between the two sides of the end
  • NioEventLoopGroup worker: The above boss gives the socket to the worker after creating the corresponding socket with the client, and the worker performs the specific communication operation.
  • NioEventLoopGroup: Asynchronous Event Loop Executor Group, which defines a set of asynchronous event executors, NioEventLoop. For the time being, it can be simply understood as the relationship between thread pools and threads, which will be specially analyzed later.
  • ServerBootstrap: A service startup boot class that allows you to quickly build a Netty application and provides a chain programming
    • group(): Configure boss and worker to simply have the current ServerBootstrap hold boss and work.
    • option(ChannelOption.SO_BACKLOG, 128): Configure parameters for channel, this example configures the number of queues to be processed for link requests 128, and link requests over 128 will be rejected
    • channel(): Configure the type of channel, using OIOServerSocketChannel and other channels in addition to NioServerSocketChannel
    • hander(): Configure the box's channel processor, that is, how to handle connection requests, which are usually distributed to the worker, so the box has a fixed request forwarding handler that forwards all link requests to the worker.
    • childHandler(): Configure the channel processor of the worker, and the read and write operations on the channel are ultimately implemented through the channel hander
    • bind(): Groups and channel s must be configured in the above configuration information, and the bind method will start the netty service and listen for the corresponding addresses and ports
    • sync(): Since the bind() method is internally asynchronous, the sync() method waits for the bind() asynchronous result
    • addListener(): Adds a listener to an asynchronous result and is notified when the result is executed
    • channel(): Gets the channel in the asynchronous execution result
    • CloseFuture: Gets the asynchronous closing result of a channel, which cannot be closed for the ServerSocketChannel port, so closeFure will not complete, for SocketChannel, when channel is called on the opposite side. The close() method will be closed, so the closeFuture will be assigned at this time
    • sync(): Wait for channel closure results on closeFuture
    • addListener(): When the channel is closed, listeners registered on the channel will be notified

Create a client and connect to it

Creating a good client logic based on the Netty framework is basically the same as the client, except that the client does not need a boss listening port and uses Bootstrap to boot the creation

class MyNettyClient {
    private static Logger logger = LogManager.getLogger(MyNettyServer.class);

    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup g = new NioEventLoopGroup(1);
        try {
            SocketChannel channel = (SocketChannel) new Bootstrap()
                    .group(g)
                    .channel(NioSocketChannel.class)
                    .handler(new MyChannelInitializer("Server"))
                    .connect(new InetSocketAddress(8888))
                    .sync()
                    .addListener(future -> logger.info("Successful client startup:" + future.isSuccess()))
                    .channel();
            channel.write("First message");
            channel.write(Stream.of("This is the second message", "Article 3", "Last message, very long message").collect(Collectors.toList()));

            channel.closeFuture()
                    .sync()
                    .addListener(future -> logger.info("Port closed,If there is an exception, the cause of the exception is:" + future.cause()));

        } finally {
            g.shutdownGracefully();
        }
    }
}

ChannelHandler for server-side boss

This ChannelHandler prints log input at different nodes of the server-side box

class MyServerChannelDuplexHandler extends ChannelDuplexHandler {
    private static Logger logger = LogManager.getLogger(MyServerChannelDuplexHandler.class);

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        logger.info("NioServerSocketChannel:" + ctx.channel() + "Successfully registered with the executor:" + ctx.executor());
        ctx.fireChannelRegistered();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        logger.info("NioServerSocketChannel:" + ctx.channel() + "Successfully added ChannelHandler:" + ctx.handler());
    }

    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        logger.info("NioServerSocketChannel:" + ctx.channel() + "Perform binding to address:" + localAddress);
        ctx.bind(localAddress, promise);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("NioServerSocketChannel:" + ctx.channel() + "Activated, ready to listen for port events");
        ctx.fireChannelActive();
    }
}

Server-side worker and client-side ChannelHander

class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    private static Log log = LogFactory.getLog(MyChannelInitializer.class);
    private static final String delimiterStr = "$_-$";
    private static final int maxFrameLen = 1024 * 1024;//1M
    private static final ByteBuf delimiter = ByteBufAllocator.DEFAULT.directBuffer(4).writeBytes(delimiterStr.getBytes());
    private String peer;

    public MyChannelInitializer(String peer) {
        this.peer = peer;
    }

    private void check(ChannelHandlerContext ctx, String msg) throws Exception {
        if (msg.length() > maxFrameLen) {
            exceptionCaught(ctx, new TooLongFrameException("Sending message content is too long"));
        }
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(maxFrameLen, delimiter));
        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new ChannelDuplexHandler() {
            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                StringBuilder res = new StringBuilder();
                if (msg instanceof String) {
                    String str = (String) msg;
                    check(ctx, str);
                    res.append(str).append(delimiterStr);
                } else if (msg instanceof List && ((List) msg).get(0) instanceof String) {
                    for (String str : (List<String>) msg) {
                        check(ctx, str);
                        res.append(str).append(delimiterStr);
                    }
                } else {
                    exceptionCaught(ctx, new Error("Only send is currently allowed String and List<String>type"));
                }
                ctx.write(res);
                ctx.flush();
            }

            @Override
            public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
                super.channelReadComplete(ctx);
            }

            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                log.info("Receive Server [" + ctx.channel().remoteAddress() + "]Message [" + msg + "]");
                if (peer == "Client") {
                    ctx.pipeline().write("Message successfully processed");
                    ctx.flush();
                }
            }
        });
    }
}

Start test

  • Start the server first, and you will get the following log information, executed in the order of log order:
    • Register channelHandler with channel
    • Register channel with executor
    • Port Binding
    • Execute Port Binding Listener
    • Channel Activation Starts Listening for Port Events
21:59:34.993 [nioEventLoopGroup-2-1] INFO  netty.server.MyServerChannelDuplexHandler - NioServerSocketChannel:[id: 0xa12ccdd6]Successfully added ChannelHandler:netty.server.MyServerChannelDuplexHandler@1a89455d
21:59:34.993 [nioEventLoopGroup-2-1] INFO  netty.server.MyServerChannelDuplexHandler - NioServerSocketChannel:[id: 0xa12ccdd6]Successfully registered with the executor: io.netty.channel.nio.NioEventLoop@43301423
21:59:34.993 [nioEventLoopGroup-2-1] INFO  netty.server.MyServerChannelDuplexHandler - NioServerSocketChannel:[id: 0xa12ccdd6]Execute binding to address:0.0.0.0/0.0.0.0:8888
21:59:35.009 [nioEventLoopGroup-2-1] INFO  netty.server.MyNettyServer - Service started successfully: true
21:59:35.009 [nioEventLoopGroup-2-1] INFO  netty.server.MyServerChannelDuplexHandler - NioServerSocketChannel:[id: 0xa12ccdd6, L:/0:0:0:0:0:0:0:0:8888]Activated, ready to listen for port events

Start Client Link to Client

When the client starts, the following log will be output, executed in the following order
-Notify listeners on connect ion asynchronous results
- Send messages to the server
- Accept server response results and output

22:01:09.043 [nioEventLoopGroup-2-1] INFO  netty.server.MyNettyServer - Successful client startup: true
22:01:09.090 [nioEventLoopGroup-2-1] INFO  netty.server.MyChannelInitializer - Receive server [0].0.0.0/0.0.0.0:8888]Message [Successfully processed message]
22:01:09.090 [nioEventLoopGroup-2-1] INFO  netty.server.MyChannelInitializer - Receive server [0].0.0.0/0.0.0.0:8888]Message [Successfully processed message]
22:01:09.090 [nioEventLoopGroup-2-1] INFO  netty.server.MyChannelInitializer - Receive server [0].0.0.0/0.0.0.0:8888]Message [Successfully processed message]
22:01:09.090 [nioEventLoopGroup-2-1] INFO  netty.server.MyChannelInitializer - Receive server [0].0.0.0/0.0.0.0:8888]Message [Successfully processed message]

Service side processes client messages

22:01:09.075 [nioEventLoopGroup-3-1] INFO  netty.server.MyChannelInitializer - Receive Server [/192.168.56.1:61353]Message [First Message]
22:01:09.075 [nioEventLoopGroup-3-1] INFO  netty.server.MyChannelInitializer - Receive Server [/192.168.56.1:61353]Message [This is the second message]
22:01:09.075 [nioEventLoopGroup-3-1] INFO  netty.server.MyChannelInitializer - Receive Server [/192.168.56.1:61353]Message [Article 3]
22:01:09.075 [nioEventLoopGroup-3-1] INFO  netty.server.MyChannelInitializer - Receive Server [/192.168.56.1:61353]Message [Last message, very long message]

Topics: Java React thread pool TCP/IP