Basic introduction of TCP packet sticking and unpacking
- 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
- 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
- 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:
- When we send 10 consecutive messages from the client to the server, the server may not receive them 10 times,
- 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
- 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
- Using custom protocol + codec to solve
- 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.