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!