netty learning note 14 - TCP packet sticking and unpacking

Keywords: codec REST socket less

Basic introduction of TCP packet sticking and unpacking

  1. TCP is connection oriented, flow oriented, and provides high reliability services. There should be one-to-one comparison of socket s on both sides of the transceiver (client and server). Therefore, in order to send multiple packets to the server more effectively to the other side, the sender uses the optimization method (Nagle algorithm) to synthesize a large block of data with multiple small intervals and small amount of data, and then packet. In this way, although it is efficient, it is difficult for the receiver to distinguish the complete packets, because flow oriented communication has no message protection boundary
  2. Because TCP has no message protection boundary, it needs to deal with the problem of message boundary at the receiving end, which is what we call packet sticking and packet splitting
  3. TCP packet sticking and unpacking diagram

    Suppose that the client sends two packets D1 and D2 to the server respectively. Since the number of bytes read by the server at one time is uncertain, the following four situations may occur:
    • The server reads two independent packets twice, D1 and D2, without sticking or unpacking
    • The server receives two packets at a time. D1 and D2 are glued together, which is called TCP gluing
    • The server reads the data packet twice, the first time it reads the complete D1 packet and part of D2 packet, and the second time it reads the rest of D2 packet, which is called TCP unpacking
    • The server reads the data package twice, the first time it reads part of D1 package, the second time it reads the rest of D1 package and the complete D2 package.

Next, let's write a program. If we don't handle it, the problem of package sticking and unpacking will occur

Client program:

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new NettyClientInitialize());

            System.out.println("Client startup...");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}


/////////////////////////////////////
public class NettyClientInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new NettyClientHandler());
    }
}

/////////////////////////////////////////
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * Channel ready triggers this method to send 10 pieces of data to the server
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            String msg = "Hello,Server, I'm the message"  +  i;
            ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Received a message from the server:" + byteBuf.toString(CharsetUtil.UTF_8));
    }
}

Server program

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        // Initialize 2 thread groups
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // Bootloader
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new NettyServerInitialize());

            System.out.println("Server startup...");
            ChannelFuture channelFuture = serverBootstrap.bind(6666).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//////////////////////////////////////////////////////////////////////
public class NettyServerInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new NettyServerHandler());
    }
}

////////////////////////////////////////////////////////////////////
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    private int count;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("The server ChannelRead Method called"+ (++count) + "frequency");
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Received a message from the client:"+ byteBuf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // Send message to client
        String msg = "Hello,I am the server  ";
        ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
    }
}

I didn't run two clients to see the test results

The server starts...
Server ChannelRead method called 1 times
 Received the message from the client: Hello, server, I am message 0Hello, server, I am message 1Hello, server, I am message 2Hello, server, I am message 3Hello, server, I am message 4Hello, server, I am message 5Hello, server, I am message 6Hello, server, I am message 7Hello, server, I am message 8Hello, server, I am message 9
 Server ChannelRead method called 1 times
 Received message from client: Hello, server, I am message 0
 Server ChannelRead method called 2 times
 Received a message from the client: Hello, server, this is message 1
 Server ChannelRead method called 3 times
 Received the message from the client: Hello, server, I am message 2 Hello, server, I am message 3 Hello, server, I am message 4
 Server ChannelRead method called 4 times
 Received message from client: Hello, server, I am message 5 Hello, server, I am message 6
 Server ChannelRead method called 5 times
 Received a message from the client: Hello, server, this is message 7
 Server ChannelRead method called 6 times
 Received a message from the client: Hello, server, this is message 8 Hello, server, this is message 9
Client startup...
Received a message from the server: Hello, I'm the server Hello, I'm the server Hello, I'm the server Hello, I'm the server Hello, I'm the server Hello, I'm the server 

From the operation results, we can analyze:

  1. When we send 10 consecutive messages from the client to the server, the server may not receive them 10 times,
  2. The server can receive all messages at one time by receiving the messages sent by the client, which may be 2 times, 3 times or 9 times, etc. This is the sticking and unpacking problem of TCP
  3. The server cannot distinguish the boundary of the message sent by the client, so we need to solve the problem of TCP packet sticking and unpacking

Solutions of sticking and unpacking TCP packets

  1. Using custom protocol + codec to solve
  2. The key is to solve the problem of the length of data read by the server every time. If this problem is solved, there will be no problem of server reading more or less, so as to avoid TCP packet sticking and unpacking.

Code example (custom protocol + codec):

  • The client is required to send 5 Message objects, one Message object at a time
  • The server receives one Message at a time and decodes it five times. Every time a Message is read, it will reply a Message object to the client

Client program

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new NettyClientInitialize());

            System.out.println("Client startup...");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

/////////////////////////////////public class NettyClientInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // Add encoder
        pipeline.addLast(new MessageEncoder());
        // Add decoder
        pipeline.addLast(new  MessageDecoder());
        pipeline.addLast(new NettyClientHandler());
    }
}

Client processor

public class NettyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    /**
     * Channel ready triggers this method to send 10 pieces of data to the server
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 5; i++) {
            String msg = "Hello,Server, I'm the message"  +  i;
            byte[] content = msg.getBytes(CharsetUtil.UTF_8);
            int length = msg.getBytes(CharsetUtil.UTF_8).length;

            // Encapsulation message
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLength(length);
            messageProtocol.setContent(content);

            ctx.writeAndFlush(messageProtocol);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
        System.out.println("Received a message from the server:" + new String(messageProtocol.getContent()));
    }
}

Protocol (key)

public class MessageProtocol {
    // Indicates the length of data sent
    private int length;
    // Content of data sent
    private byte[] content;
    }

Decoder and encoder (critical)

/**
 * Encoder
 */
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
        System.out.println("Encoder called");
        byteBuf.writeInt(messageProtocol.getLength());
        byteBuf.writeBytes(messageProtocol.getContent());
    }
}
/**
 * Decoder
 */
public class MessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        System.out.println("Decoder called");
        // Get message length and content
        int length = byteBuf.readInt();
        byte[] content = new byte[length];
        byteBuf.readBytes(content);

        // Encapsulate the message into a MessagePool, put it into a list, and deliver the next handler business processing
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLength(length);
        messageProtocol.setContent(content);

        list.add(messageProtocol);
    }
}

Server program

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        // Initialize 2 thread groups
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // Bootloader
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new NettyServerInitialize());

            System.out.println("Server startup...");
            ChannelFuture channelFuture = serverBootstrap.bind(6666).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class NettyServerInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // Add codec
        pipeline.addLast(new MessageEncoder());
        pipeline.addLast(new MessageDecoder());
        pipeline.addLast(new NettyServerHandler());
    }
}

Server processor

public class NettyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    private int count;

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
        System.out.println("The server channelRead0 Method called"+ (++count) + "frequency");
        byte[] content = messageProtocol.getContent();
        System.out.println("Received a message from the client:"+ new String(content));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // Send message to client
        String msg = "Hello,I am the server  ";
        byte[] content = msg.getBytes(CharsetUtil.UTF_8);
        int length = msg.getBytes(CharsetUtil.UTF_8).length;
        // Encapsulated object
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLength(length);
        messageProtocol.setContent(content);
        ctx.writeAndFlush(messageProtocol);
    }
}

Run results (multiple customers started):

Server startup...
Decoder called
 Server channelRead0 method called 1 times
 Received message from client: Hello, server, I am message 0
 Decoder called
 Server channelRead0 method called 2 times
 Received a message from the client: Hello, server, this is message 1
 Decoder called
 Server channelRead0 method called 3 times
 Received a message from the client: Hello, server, this is message 2
 Decoder called
 Server channelRead0 method called 4 times
 Received a message from the client: Hello, server, this is message 3
 Decoder called
 Server channelRead0 method called 5 times
 Received a message from the client: Hello, server, this is message 4
 Encoder called
 Decoder called
 Server channelRead0 method called 1 times
 Received message from client: Hello, server, I am message 0
 Encoder called
 Decoder called
 Server channelRead0 method called 2 times
 Received a message from the client: Hello, server, this is message 1
 Encoder called
 Decoder called
 Server channelRead0 method called 3 times
 Received a message from the client: Hello, server, this is message 2
 Decoder called
 Server channelRead0 method called 4 times
 Received a message from the client: Hello, server, this is message 3
 Encoder called
 Decoder called
 Server channelRead0 method called 5 times
 Received a message from the client: Hello, server, this is message 4

From the results, we can know that the client sends 5 messages, the client receives 5 messages according to the protocol, and the test results of enabling multiple clients are the same, which perfectly results in the problem of TCP packet sticking and unpacking,

The key to solve the problem is to define the protocol and codec to send the message according to the protocol and codec, which results in the problem of TCP packet splitting and sticking.

Published 65 original articles, won praise 3, visited 7886
Private letter follow

Posted by ProblemHelpPlease on Fri, 21 Feb 2020 00:06:39 -0800