I heard that wechat search “Java fish” will change strong oh!

This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview

(a) what is sticky package, half package

In the actual network development or in the interview, the beginning of the use of TCP protocol often encounter sticky packet and half packet situation, so we need to understand what is sticky packet, what is half packet, and how to solve the problem.

Sticky packet: The name means that the packets sent between the client and the server are stuck together. The packets that should be sent separately are stuck together.

Half packet: A packet is divided into multiple packets to be sent.

The root cause of sticky packets and half packets is that in TCP, there is only the concept of stream, but no concept of packet. There is no boundary after the message is sent to the buffer, so sticky packets and half packets will occur.

(2) Effect demonstration of sticking half package

Netty (netty) netty (netty)

The first is the sticky package: the server code looks like this, nothing else special:

public class FirstServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg); }}); } }) .bind(8080); }}Copy the code

The client code sends hello ten times through a for loop as it sends data:

public class FirstClient {
    public static void main(String[] args) throws InterruptedException {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect(new InetSocketAddress("localhost".8080))
                .sync()
                .channel();
        for (int i = 0; i < 10; i++) {
            channel.writeAndFlush("hello"); }}}Copy the code

As a result, sticky packets occurred, and the data that should have been sent ten times was sent all at once

This is followed by a half-package, adding a line of code on the server side:

public class FirstServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                // Change the buffer size to a smaller size
                .option(ChannelOption.SO_RCVBUF,4)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg); }}); } }) .bind(8080); }}Copy the code

Then run again: both sticky packets and half packets occur.

(3) Analysis of reasons for sticking half a package

After seeing the effect, we will analyze why there will be sticky half package.

In TCP, an ACK is required every time data is sent, but this means that data will be sent serially. So TCP introduced a concept called sliding Windows.

A sliding window is actually a buffer, and data sent within the sliding window can continue to be sent without receiving a response. When the first data ACK is confirmed, the sliding window is moved down one unit. The rough flow chart looks something like this:

If the sliding window of the receiver is large enough and the receiver does not process the data in time, the data sent by the sender will buffer multiple packets in the sliding window of the receiver, resulting in sticky packets.

When the sliding window setting of the receiver is less than the actual amount of data to be sent, only part of the data can be processed first, and the subsequent data can be processed after ack confirmation, resulting in the situation of half packets.

In addition to the TCP layer, the Nagle algorithm can also cause sticky packets, and the NETWORK card’s MSS limit can also cause half packets.

(4) Sticking package, half package solution

Two solutions are provided to solve the problem of sticking half package:

1. Specify the length of the message, at both the sender and the receiver

2. Insert delimiters in data

Here is the sticky package solution in Netty:

4.1 Specifying the length of the message (fixed-length decoder)

Netty provides a fixed-length decoder that specifies the length of a message for scenarios where the length of a character is the same each time it is sent. Modify the server side, set the length of the fixed length decoder to 5:

public class FirstServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg); }}); } }) .bind(8080); }}Copy the code

The client sends data with a length of 5 each time:

public class FirstClient {
    public static void main(String[] args) throws InterruptedException {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        // Encode the content to be sent
                        nioSocketChannel.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect(new InetSocketAddress("localhost".8080))
                .sync()
                .channel();
        for (int i = 0; i < 10; i++) {
            channel.writeAndFlush("hello"); }}}Copy the code

Observe the final result:

The disadvantage of this method is that the data of uncertain length cannot be split.

4.2 Specifying delimiters (delimiter decoder)

Netty provides the separator decoder, can obtain the carriage return separator “\n” or “\r\n”, directly modify the above code:

The server side//nioSocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
nioSocketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024)); The client//channel.writeAndFlush("hello");
channel.writeAndFlush("hello"+"\n");
Copy the code

4.3 LTC decoder

Finally, one of the most practical decoder LengthFieldBasedFrameDecoder, LTC encoder to make up for a fixed-length encoder can only limit the length of the faults, practical more flexible.

There are four important parameters in the LTC decoder:

1, lengthFieldOffset: offset of length field

LengthFieldLength: Specifies the length of a field

LengthAdjustment: How many bytes after the length field is the content

4. Initialbyteststrip: Indicates the number of bytes to be skipped

We can understand the four parameters by using this example in the source code:

LengthFieldOffset = 1 HDR1 = 1

LengthFieldLength is equal to 2

3. After the length field, 1 byte is followed by the content, so lengthAdjustment equals 1

4. Initialbyteststrip is set to 3, so the output is HDR2+ActualContent after decoding

Is implemented in the code is given below: the first is a server-side code, increased LengthFieldBasedFrameDecoder decoder

public class FirstServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        //nioSocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(5));
                        nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024.0.4.0.4));
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg.toString()); }}); } }) .bind(8080); }}Copy the code

Client code to increase the length of data when the client writes data:

public class FirstClient {
    public static void main(String[] args) throws InterruptedException {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new LengthFieldPrepender(4.false));
                    }
                })
                .connect(new InetSocketAddress("localhost".8080))
                .sync()
                .channel();
        String[] contentList=new String[]{"hello"."helloWorld"."hello, world"};
        for (int i = 0; i <3; i++) { String content=contentList[i];byte[] data = content.getBytes();
            ByteBuf buf= Unpooled.buffer();
            buf.writeBytes(data,0,data.length); channel.writeAndFlush(buf); }}}Copy the code

(5) Summary

Here about sticky packet and half packet and netty solve sticky packet problem here comes to an end, network programming want to learn this road just started, I am fish boy, we see you next time!