netty encoding and decoding and packet sticking and unpacking processing

Posted by mise_me_fein on Fri, 04 Mar 2022 14:30:26 +0100

netty encoding and decoding and packet sticking and unpacking processing

Codec

When you send or receive a message through Netty, a data conversion will occur. The inbound message will be decoded: from byte to another format (such as java object); If it is an outbound message, it will be encoded into bytes.
Netty provides a series of practical codecs. They all implement the ChannelInboundHadnler or ChannelOutboundHandler interface. In these classes, the channelRead method has been overridden. Taking inbound as an example, this method will be called for each message read from inbound Channel. Then, it will call the decode() method provided by the known decoder to decode and forward the decoded bytes to the next ChannelInboundHandler in the ChannelPipeline.

If you want to achieve efficient encoding and decoding, you can use protobuf, but protobuf needs to maintain a large number of proto files, which is more troublesome. Now you can generally use protobuf.
Protopuff is a serialization method based on protopuff implementation. Its most obvious advantage over protopuff is that it doesn't need us to write without losing performance proto file to achieve serialization.

Sticking and unpacking

TCP is a streaming protocol, which is a long string of binary data without boundaries. As a transport layer protocol, TCP does not know the specific meaning of the upper layer service data. It will divide the data packets according to the actual situation of the TCP buffer. Therefore, it is considered as a complete packet in terms of service. It may be split into multiple packets for transmission by TCP, or multiple small packets may be encapsulated into one large packet for transmission, This is the so-called TCP packet sticking and unpacking problem. Flow oriented communication has no message protection boundary.

<!-- introduce netty package-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.35.Final</version>
        </dependency>

        <!--use protostuff Implement serialization-->
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-api</artifactId>
            <version>1.0.10</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.10</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.10</version>
        </dependency>

Use the LengthFieldBasedFrameDecoder as the unpacker

For details, please refer to this article netty source code analysis of LengthFieldBasedFrameDecoder

Unpacker

public class PackageSplitUtil extends LengthFieldBasedFrameDecoder {

    public PackageSplitUtil() {
        super(Integer.MAX_VALUE, Constant.LENGTH_OFFECT, Constant.LENGTH_BYTES_COUNT, 0, Constant.LENGTH_BYTES_COUNT);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{
        return super.decode(ctx, in);
    }
}

encoder

public class MyNettyEncoder extends MessageToByteEncoder<NettyMessage> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage msg, ByteBuf out) throws Exception {
        byte[] bytes = ProtostuffUtil.serializer(msg);
        // Gets the length of the message
        int length = bytes.length;
        ByteBuf buf = Unpooled.buffer(Constant.HEAD_LEAGTH + length);
        // Write protocol version
        buf.writeShort(Constant.PROTOCOL_VERSION);
        // Write message length
        buf.writeShort(length);
        // Write message body
        buf.writeBytes(bytes);
        out.writeBytes(buf);

    }
}

decoder

public class MyNettyDecoder extends ByteToMessageDecoder {

    private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        // Read the length of the transmitted message.
        int length = in.readUnsignedShort();
        // If the length is less than 0, the data is illegal and the connection is closed
        if (length < 0) {
            ctx.close();
        }
        // If the length of the read message body is less than the length of the transmitted message
        if (length > in.readableBytes()) {
            log.info("The read message length is less than the transmitted message length!");
            return;
        }
        ByteBuf frame = Unpooled.buffer(length);
        in.readBytes(frame);
        try {
            byte[] inByte = frame.array();
            // Byte to object
            NettyMessage msg = ProtostuffUtil.deserializer(inByte, NettyMessage.class);

            if (msg != null) {
                // Get business message header
                out.add(msg);
            }
        } catch (Exception e) {
            log.error("====" + ctx.channel().remoteAddress() + ",decode failed====");
        }
    }
}

Server use

public class NettyServer {

    private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);

    private static Channel serverChannel;

    private static final NettyServer nettyServer = new NettyServer();

    public static NettyServer getInstance(){ return nettyServer; }

    private NettyServer(){
        start();
    }

    public static void closeServer(){
        if (nettyServer != null){
            serverChannel.close();
            serverChannel = null;
        }
    }
    private static void start(){
        //Create a thread to process connection requests
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //Create threads to interact with clients
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //Create the startup object of the server
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {//Create a channel initialization object and set initialization parameters
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            log.info("[ client ]" + ch.remoteAddress() + " Connected");
                            //Set the processor for the socketChannel of the workGroup
                            ChannelPipeline pipeline = ch.pipeline();

                            //heartbeat mechanism 
                            pipeline.addLast(new IdleStateHandler(Constant.HEART_TIMEOUT, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast(new PackageSplitUtil());
                            //decoder
                            pipeline.addLast(new MyNettyEncoder());
                            //encoder
                            pipeline.addLast(new MyNettyDecoder());
                            //Custom processor
                            pipeline.addLast(new NettyServerHandler());

                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(Constant.NETTY_SERVER_PORT).sync();
            serverChannel = channelFuture.channel();
            log.info("====netty The server has started successfully==== Port:" + Constant.NETTY_SERVER_PORT);
            //Monitor the channel closing. closeFuture is an asynchronous operation. Monitor the channel closing
            serverChannel.closeFuture().addListener((ChannelFutureListener) future -> {
                log.info("====Gracefully closed EventLoopGroup====");
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
        }
    }
}

Constant partial parameters

/**
     * Agreed header length
     */
    public static int HEAD_LEAGTH = 2;

    /**
     * Agreed version number
     */
    public static int PROTOCOL_VERSION = 1;

    /**
     * Agreed length offset (2 bytes)
     */
    public static int LENGTH_OFFECT = 2;

    /**
     * Agreed length bytes (short type)
     */
    public static int LENGTH_BYTES_COUNT = 2;

    /**
     * port
     */
    public static int NETTY_SERVER_PORT = 9000 ;

Topics: Java Netty socket