Introduction to Netty and implementation of simple communication framework

1. What is netty

Netty is a client / server framework that uses Java's advanced network capabilities to hide the complexity behind it and provide an easy-to-use API. It is active and growing in user communities, such as large companies Facebook and Instagram, as well as popular open source projects such as infinispan, hornetq and vert x. Apache Cassandra and Elasticsearch all use their powerful core code for network abstraction.
To put it bluntly, Netty is a java high-performance network communication framework. It uses the NIO communication model provided by the bottom layer of the operating system to realize high-performance network communication. At present, it has been widely used in various middleware.

2. Quick start

Here you can directly access the server code. The code level of netty is simple and easy to understand. It is a very typical builder mode

public static void main(String[] args) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //Processing channel
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new IdleHandler());
                            p.addLast(new LoggingHandler(LogLevel.INFO));
                            //Message decoding
                            p.addLast(new MyEncoder());
                            p.addLast(new MyDecoder(1024, 4, 4, 0, 0));
                            //Business processing
                            p.addLast(new ServerSingleHandler());
                            p.addLast(new ServerGroupHandler());
                        }
                    });
            //Binding port
            ChannelFuture f = b.bind(8090).sync();
            f.channel().closeFuture().sync();
        } finally {
            //Close after processing the remaining requests
            workerGroup.shutdownGracefully();
        }
    }

The coding of the client is basically the same

 public static void main(String[] args) throws Exception {
        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE,true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new MyEncoder());
                     p.addLast(new MyDecoder(1024, 4, 4, 0, 0));
                     p.addLast(new ClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect("127.0.0.1", 8090).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }

3.Netty's Handler responsibility chain model


You can see the method p.addLast() in the above code. Here is to add logical handlers in sequence. Multiple handlers will be constructed into a pipeline, that is, a responsibility chain for logical processing, to process the incoming information in sequence. Handlers are also divided into inboundHandler and outboundHandler. Therefore, when the server is initialized, We can easily and frankly complete the logical processing flow of access information

4. Encoder and decoder

To implement a custom decoder, you only need to inherit the encoder/decoder built in the netty framework. There are several simple codecs built in netty:

Fixed length framedecoder
Line decoder LineBasedFrameDecoder
DelimiterBasedFrameDecoder based on delimiter
Length field based framedecoder
Custom format codemessagetobyteencoder
Let's take the codec used in the above example as an example to see how to use these built-in classes

Here, we use the format of flag header (an int)+body length (an int)+body entity

public class MyEncoder extends MessageToByteEncoder<MyProtocol> {
    //Just override the encode method
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MyProtocol myProtocol, ByteBuf byteBuf)  {
        byteBuf.writeInt(MyProtocol.header);//0x76
        byteBuf.writeInt(myProtocol.getContentLength());
        byteBuf.writeBytes(myProtocol.getContent());
    }
}

After we know the message body format, the rest is to parse the message body through the custom decoder. Here, because there is body length, the length field based decoder is applicable.
At this time, we can cooperate to see the parameters of the decoder in the above example.

According to the settings, the maximum length of the message body is 1024 bytes, and the offset of the body length field is 4 bytes (because the first 4 bytes are the identification number 0)
x76), the body length field is 4 bytes (an int). That is, the length of the message body header is 8 bytes. After parsing the body length in the header, we can accurately intercept the body, so that a complete message body can be parsed.

! Note that the subsequent business logic should also pay attention to the coding format. For example: messageentity=
GsonUtil.GsonToBean(new String(myProtocol.getContent(), "UTF-8"),
MessageEntity.class);

The decoder code is as follows:

public class MyDecoder extends LengthFieldBasedFrameDecoder {
    public MyDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //Call the method of the parent class here to get the desired part. I want all of them here, or just the body part
        in = (ByteBuf) super.decode(ctx, in);
        if (in == null) {
            return null;
        }

        int header = in.readInt();
        if (header != MyProtocol.header) {
            throw new Exception("Unrecognized protocol");
        }
        //Read length field
        int length = in.readInt();

        if (in.readableBytes() != length) {
            throw new Exception("The length of the mark does not match the actual length");
        }
        //Read body
        byte[] bytes = new byte[length];
        in.readBytes(bytes);

        return new MyProtocol(length, bytes);

    }
}

Attached: demo code

Added by srboj on Tue, 21 Dec 2021 07:25:28 +0200