This is the 18th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021″

1. Phenomenon analysis

1.1 stick package

Use code to demonstrate the phenomenon of sticky packets:

Server:

public class HalfPackageServer { public static void main(String[] args) { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.group(boss, worker); Serverbootstrap. option(channeloption.so_rcvbuf,10); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override Public void channelRead(ChannelHandlerContext CTX, Object MSG) throws Exception { Again, 160 bytes printBuf((ByteBuf) MSG); super.channelRead(ctx, msg); }}); }}); ChannelFuture channelFuture = serverBootstrap.bind(8080); // block waiting for channelfuture.sync (); // block pending release connection channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("server error:" + e); Exceptionexception () : exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception))))); worker.shutdownGracefully(); } } static void printBuf(ByteBuf byteBuf){ StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i< byteBuf.writerIndex(); i++) { stringBuilder.append(byteBuf.getByte(i)); stringBuilder.append(" "); } the stringBuilder. Append (" | length: "); stringBuilder.append(byteBuf.writerIndex()); StringBuilder. Append (" byte "); System.out.println(stringBuilder); }}Copy the code

Client:

public class HalfPackageClient { public static void main(String[] args) { NioEventLoopGroup worker = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.channel(NioSocketChannel.class); bootstrap.group(worker); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) Throws the Exception {ch. Pipeline (.) addLast (new ChannelInboundHandlerAdapter () {/ / after the success of the connection is established, @Override public void channelActive(ChannelHandlerContext CTX) throws Exception {// Send the packet in a loop with 16 bytes each. For (int I = 0; i < 10; i++) { ByteBuf buffer = ctx.alloc().buffer(); buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); ctx.writeAndFlush(buffer); }}}); }}); ChannelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); // Release the connection channelfuture.channel ().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("client error :" + e); } the finally {/ / release EventLoopGroup worker. ShutdownGracefully (); }}}Copy the code

Results:

0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 12 3 4 5 6 7 8 9 10 11 12 13 14 15 0 2 3 4 5 6 7 8 8 9 10 11 12 13 14 15 | length: 160 bytesCopy the code

As shown above, instead of sending 16 bytes 10 times, a 160 byte is received instead of 10 16 bytes. This is the sticky bag phenomenon.

1.2 half a pack

For the half-packet phenomenon, we still use the previous code, but the server needs to set one more property, that is, to change the size of the server’s receiving buffer, we changed it to 10 bytes.

serverBootstrap.option(ChannelOption.SO_RCVBUF,10);

The value of ChannelOption is not 10 bytes. After tracing the source code, I found that the generic type of ChannelOption is an Ingteger, so I guess I calculated the type. An Integer is four bytes, so with the setting of 10, you end up receiving 40 bytes.

If you think this question is wrong, please help me correct it. Thank you!!!!!

public static final ChannelOption SO_RCVBUF = valueOf(“SO_RCVBUF”);

Server code:

public class HalfPackageServer { public static void main(String[] args) { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.group(boss, worker); Serverbootstrap. option(channeloption.so_rcvbuf,10); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override Public void channelRead(ChannelHandlerContext CTX, Object MSG) throws Exception { Again, 160 bytes printBuf((ByteBuf) MSG); super.channelRead(ctx, msg); }}); }}); ChannelFuture channelFuture = serverBootstrap.bind(8080); // block waiting for channelfuture.sync (); // block pending release connection channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("server error:" + e); Exceptionexception () : exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception))))); worker.shutdownGracefully(); } } static void printBuf(ByteBuf byteBuf){ StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i< byteBuf.writerIndex(); i++) { stringBuilder.append(byteBuf.getByte(i)); stringBuilder.append(" "); } the stringBuilder. Append (" | length: "); stringBuilder.append(byteBuf.writerIndex()); StringBuilder. Append (" byte "); System.out.println(stringBuilder); }}Copy the code

The client is the same as the previous sticky package.

Results:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 | length: 36 bytes 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 | length: 12 13 14 15 40 bytes 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 | length: 40 bytes 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 | length: 40 bytes 12 13 14 15 | length: 4 bytesCopy the code

As shown above, a total of 160 bytes were sent five times, each with an incomplete 16 bytes of data. This is the half packet, which actually contains the sticky packet.

As to why the first connection is only 36, there is no specific analysis here, but we can guess that the first connection is represented by 4 bytes.

Two, sticky package, half package analysis

The nature of sticky packets and half packets: TCP is a streaming protocol and messages are borderless.

2.1 Sliding Window

TCP is a reliable outgoing protocol that requires an ACK for each segment sent. If an ACK is not received, it will be sent again.

But if each request is sent by a client and the next request is not sent until the other client is ready, the entire configuration becomes serialized, greatly reducing the transmission efficiency between connections.

How does TCP solve the efficiency problem?

Introduce sliding Windows. The window size determines the maximum amount of data that can continue to be sent without waiting for a reply.

The simple sliding process is as follows:

The window actually acts as a buffer and also acts as a flow control.

  • Only data within the window is allowed to be sent (green), and the window must stop sliding until the reply (blue) arrives
  • If the ack from 0 to 100 comes back, the window can slide down and a new ack will be added for sending.
  • The receiver also maintains a window, and only data that falls within the window is allowed to be received

2.2 Analysis of sticky bags

  • Phenomenon, sending 16 bytes 10 times, receiving a 160 byte instead of 10 16 bytes.

  • Causes:

    • Application layer: Receiver ByteBuf setting is too large (Netty default 1024)
    • Sliding window (TCP) : Assume that the 256 bytes of the sender represents a complete packet. However, the 256 bytes are cached in the sliding window of the receiver because the receiver does not process the packet in time and the window size is large enough. If multiple packets are cached in the sliding window of the receiver, the packet will be stuck.
    • Nagle algorithm (TCP) : Sticky packets

2.3 Analysis of half-packet phenomenon

  • The previous half-packet example code sent 16 bytes 10 times, and the received data was partially phased, not the full 16 bytes.

  • The reasons causing

    • Application layer: ByteBuf on the receiving end is smaller than the actual amount of data sent
    • Sliding window (TCP) : Suppose that the window of the receiver is only 128 bytes, and the size of the packet of the sender is 256 bytes. In this case, there is no room for it. The first 128 bytes can only be sent and the rest can be sent after ack
    • MSS limit: When the sent data exceeds the MSS limit, the data will be sliced and sent, resulting in half packets

2.4 Extension: Nagle algorithm and MSS limitation

Against 2.4.1 Nagle algorithm

In TCP, an algorithm called Nagle is often used to improve network utilization.

The algorithm is a processing mechanism that delays the sending of data even if there is still some data that should be sent.

If neither of the following conditions is met, the device sends the packet after a period of time:

  • An acknowledgement has been received for all data that has been sent
  • Maximum segment length (MSS) data can be sent

According to this algorithm, although network utilization can be improved, some degree of delay may occur. It is often necessary to turn off the algorithm for time-sensitive systems.

Conditions in Netty are as follows:

  • If the SO_SNDBUF data reaches the maximum segment size (MSS), the data needs to be sent.
  • If the SO_SNDBUF contains FIN (indicating that the connection needs to be closed), the remaining data is sent and then closed.
  • If TCP_NODELAY = true, the packet is sent directly.
  • If the sent data receives an ACK, it needs to be sent.
  • If the preceding conditions are not met, you need to send the packet if the timeout (usually 200ms) occurs.
  • In addition to the above, delay sending

2.4.2 MSS limit

MSS limit

The data link layer has restrictions on the maximum amount of data that can be sent at one time. Each data link has a different maximum transmission unit (MTU).

  • The MTU of the Ethernet is 1500
  • The MTU of FDDI is 4352
  • The MTU of the local loopback address is 65535 – the local test does not go to the nic

MSS is the maximum segment size. It is the number of bytes that the MTU can transmit after removing TCP and IP headers

  • The ipv4 TCP header occupies 20 bytes and the IP header occupies 20 bytes. Therefore, the Ethernet MSS value is 1500-40 = 1460
  • When TCP transfers a large amount of data, the data is divided and sent according to the MSS size
  • The value of the MSS notifies the other party of its MSS value during the three-way handshake, and then selects a small value between the two as the MSS

Three, sticky package and half package solution

3.1 short connection

Whenever the client. After sending a message, it disconnects from the server.

We need to modify the client code, in fact, to extract the internal loop of sending 10 messages, only one message per connection, 10 connections need to be established, I won’t demonstrate this, do not have to think about the result:

  • Client send must not produce sticky packet problem.
  • As long as the server receive buffer is large enough, the problem of half packet will not occur, but it is not guaranteed.

Disadvantages of this scheme: 1) To establish a large number of links, low efficiency. 2) Can not solve the problem of half package.

3.2 Fixed-length messages

Netty provides an inbound FixedLengthFrameDecoder for fixed-length messages, which specifies the fixed length of the received message.

Server:

public class FixedLengthServer { public static void main(String[] args) { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.group(boss, worker); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FixedLengthFrameDecoder(8)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object MSG) throws Exception {// printBuf((ByteBuf) MSG); super.channelRead(ctx, msg); }}); }}); ChannelFuture channelFuture = serverBootstrap.bind(8080); // block waiting for channelfuture.sync (); // block pending release connection channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("server error:" + e); Exceptionexception () : exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception))))); worker.shutdownGracefully(); } } static void printBuf(ByteBuf byteBuf) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < byteBuf.writerIndex(); i++) { stringBuilder.append(byteBuf.getByte(i)); stringBuilder.append(" "); } the stringBuilder. Append (" | length: "); stringBuilder.append(byteBuf.writerIndex()); StringBuilder. Append (" byte "); System.out.println(stringBuilder); }}Copy the code

Client:

public class FixedLengthClient { public static void main(String[] args) { NioEventLoopGroup worker = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.channel(NioSocketChannel.class); bootstrap.group(worker); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) Throws the Exception {ch. Pipeline (.) addLast (new ChannelInboundHandlerAdapter () {/ / after the success of the connection is established, @Override public void channelActive(ChannelHandlerContext CTX) throws Exception {// Sending data packets with Random content is Random r = new Random(); char c = 1; ByteBuf buffer = ctx.alloc().buffer(); for (int i = 0; i < 10; i++) { byte[] bytes = new byte[8]; for (int j = 0; j < r.nextInt(8); j++) { bytes[j] = (byte) c; } c++; buffer.writeBytes(bytes); } ctx.writeAndFlush(buffer); }}); }}); ChannelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); // Release the connection channelfuture.channel ().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("client error :" + e); } the finally {/ / release EventLoopGroup worker. ShutdownGracefully (); }}}Copy the code

Results:

1 1 0 0 0 0 0 0 | length: 8 byte 2 2 2 2 0 0 0 0 | length: 8 bytes 3 0 0 0 0 0 0 0 | length: 8 bytes 4 4 4 0 0 0 0 0 | length: 8 bytes 5 5 5 0 0 0 0 | length: 6 8 bytes 0 0 0 0 0 0 0 | length: 7 8 bytes 0 0 0 0 0 0 0 | length: 8 8 8 bytes 0 0 0 0 0 0 | length: 9 9 9 8 bytes 0 0 0 0 0 | length: 8 bytes 10 10 10 10 10 0 0 0 | length: 8 bytesCopy the code

Using fixed length also has certain disadvantages, the message length is not easy to determine: 1) too large, resulting in space waste. 2) Too small, may not be enough for some packets.

3.3 the delimiter

1) Netty provides lineBase framedecoder.

The default separator is \n or \r\n. The maximum length needs to be specified. If the specified length is exceeded and no separator is found, an exception will be thrown.

2) Netty provides DelimiterBasedFrameDecoder (custom delimiter frame decoder). You need to specify a separator of type ByteBuf, and you need to specify the maximum length.

LineBasedFrameDecoder Example code:

The service side

public class LineBasedServer { public static void main(String[] args) { NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.group(boss, worker); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(byteBuf.toString(StandardCharsets.UTF_8)); super.channelRead(ctx, msg); }}); }}); ChannelFuture channelFuture = serverBootstrap.bind(8080); // block waiting for channelfuture.sync (); // block pending release connection channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("server error:" + e); Exceptionexception () : exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception))))); worker.shutdownGracefully(); }}}Copy the code

Client:

public class LineBasedClient { public static void main(String[] args) { NioEventLoopGroup worker = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.channel(NioSocketChannel.class); bootstrap.group(worker); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) Throws the Exception {ch. Pipeline (.) addLast (new ChannelInboundHandlerAdapter () {/ / after the success of the connection is established, @Override public void channelActive(ChannelHandlerContext CTX) throws Exception {// Send delimited data packets ByteBuf buffer = ctx.alloc().buffer(); String str = "hello world\nhello world\n\rhello world\nhello world"; buffer.writeBytes(str.getBytes(StandardCharsets.UTF_8)); ctx.writeAndFlush(buffer); }}); }}); ChannelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); // Release the connection channelfuture.channel ().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("client error :" + e); } the finally {/ / release EventLoopGroup worker. ShutdownGracefully (); }}}Copy the code

Results:

hello world
hello world
hello world
Copy the code

DelimiterBasedFrameDecoder code example, roughly the same as the first and only offer different location of the code below:

Server:

protected void initChannel(SocketChannel ch) throws Exception { ByteBuf buffer = ch.alloc().buffer(); buffer.writeBytes("||".getBytes(StandardCharsets.UTF_8)); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buffer)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(byteBuf.toString(StandardCharsets.UTF_8)); super.channelRead(ctx, msg); }}); }Copy the code

Client:

String str = "hello world||hello world||hello world||hello world";
Copy the code

This approach to using delimiters has its drawbacks: character data is handled well, but if the content itself contains delimiters, parsing errors will occur. Byte by byte comparison, phase ratio is not very good.

3.4 Preset Length

LengthFieldBasedFrameDecoder field length decoder, allow us to send the message, specify the length of the message, and then according to the length of the number of bytes read response.

public LengthFieldBasedFrameDecoder(
            int maxFrameLength,
            int lengthFieldOffset, 
            int lengthFieldLength,
            int lengthAdjustment, 
            int initialBytesToStrip) 
Copy the code

Focusing on the five-field constructor, let’s look at what each field means.

Field meanings are listed here first:

MaxFrameLength: indicates the maximum length. LengthFieldOffset: Offset of the length field. LengthFieldLength: Indicates the length of a field. LengthAdjustment: Based on the length field, and a few bytes of content. Initialbyteststrip: To strip a few bytes from scratch.

Explain what these fields mean by using Netty’s examples.

Example 1:

  • lengthFieldOffset = 0
  • lengthFieldLength = 2
  • lengthAdjustment = 0
  • initialBytesToStrip = 0

LengthFieldLength (0x000C) specifies that the length of the message is 12:

Length Actual Content
0x000C “HELLO, WORLD”

After receiving the parse, it is still 14 bytes:

Length Actual Content
0x000C “HELLO, WORLD”

Example 2:

  • lengthFieldOffset = 0
  • lengthFieldLength = 2
  • lengthAdjustment = 0
  • initialBytesToStrip = 2

Send before, a total of 14 bytes, including lengthFieldLength occupy two bytes, 0 x000c said the message length is 12, initialBytesToStrip said stripping 2 bytes, from the very beginning is parsed, the length of the field a stripping away of the:

Length Actual Content
0x000C “HELLO, WORLD”

After receiving the parse, there are only 12 bytes:

Actual Content
“HELLO, WORLD”

Example 3:

  • lengthFieldOffset = 2
  • lengthFieldLength = 3
  • lengthAdjustment = 0
  • initialBytesToStrip = 0

LengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength: lengthFieldLength:

Header 1 Length Actual Content
0xCAFE 0x00000C “HELLO, WORLD”

After receiving the parse, there are only 17 bytes:

Header 1 Length Actual Content
0xCAFE 0x00000C “HELLO, WORLD”

Example 4:

  • lengthFieldOffset = 0
  • lengthFieldLength = 3
  • lengthAdjustment = 2
  • initialBytesToStrip = 0

LengthFieldLength; lengthAdjustment; lengthFieldLength; lengthFieldLength; lengthFieldLength;

Length Header 1 Actual Content
0x00000C 0xCAFE “HELLO, WORLD”

After receiving the parse, there are only 17 bytes:

Length Header 1 Actual Content
0x00000C 0xCAFE “HELLO, WORLD”

Example 5:

  • lengthFieldOffset = 1
  • lengthFieldLength = 2
  • lengthAdjustment = 1
  • initialBytesToStrip = 3

The length field is 1 byte cheaper, and the length field is 2 bytes. The length field starts with the length field, and the content is 1 byte after the length field. The first three bytes are ignored.

Header 1 Length Header 2 Actual Content
0xCA 0x000C 0xFE “HELLO, WORLD”

After receiving the parse, there are only 13 bytes:

Header 2 Actual Content
0xFE “HELLO, WORLD”

To simulate example 5, write an example code with a slightly different content:

Sample code:

The service side

/** * @description: TODO * @author: weirx * @date: 2021/11/12 15:34 * @version: 3.0 */ public class LineBasedServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.group(boss, worker); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void InitChannel SocketChannel (ch) throws the Exception {ch. Pipeline () addLast (new LengthFieldBasedFrameDecoder,1,4,1,5 (1024)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(byteBuf.readByte() + "|" + byteBuf.toString(StandardCharsets.UTF_8)); super.channelRead(ctx, msg); }}); }}); ChannelFuture channelFuture = serverBootstrap.bind(8080); // block waiting for channelfuture.sync (); // block pending release connection channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("server error:" + e); Exceptionexception () : exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception (exceptionexception))))); worker.shutdownGracefully(); }}}Copy the code

Client:

public class LineBasedClient { public static void main(String[] args) { NioEventLoopGroup worker = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.channel(NioSocketChannel.class); bootstrap.group(worker); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) Throws the Exception {ch. Pipeline (.) addLast (new ChannelInboundHandlerAdapter () {/ / after the success of the connection is established, @Override public void channelActive(ChannelHandlerContext CTX) throws Exception {send(CTX," Hello, world"); send(ctx,"HI!" ); }}); }}); ChannelFuture = bootstrap.connect("127.0.0.1", 8080).sync(); // Release the connection channelfuture.channel ().closeFuture().sync(); } catch (InterruptedException e) { System.out.println("client error :" + e); } the finally {/ / release EventLoopGroup worker. ShutdownGracefully (); } } static void send(ChannelHandlerContext ctx,String msg){ ByteBuf buffer = ctx.alloc().buffer(); byte[] bytes = msg.getBytes(); int length = bytes.length; // Write Header 1 buffer.writeByte(1); // Write the length buffer. WriteInt (length); // Write Header 2 buffer.writeByte(2); // Write the last content buffer.writebytes (bytes); ctx.writeAndFlush(buffer); }}Copy the code

Results:

2|hello, world
2|HI!
Copy the code

So much for sticky bags and half bags. If it’s useful, please give me a thumbs up