This is the eighth article in the Netty series

In the last article we took an in-depth look at ChannelHandler and ChannelPipeline, the core components of Netty’s logical architecture, and introduced best practices for daily development use. ChannelHandler is mainly used for data input and output processing, such as codec and exception handling.

Today, we’ll take a look at one of the most common uses of ChannelHandler in everyday development — codecs.

If ChannelHandler learning is the foundation of Netty, then codec is derived from “basic” “common moves”, we tend to use a ChannelHandler to implement the codec logic. Whether it is network programming actual combat, or interview essay, are inseparable from the knowledge of coding and decoding.

This article will take about 15 minutes to read and will focus on the following questions:

  • Learn codecs, starting with sticky/unpack
  • How to implement a custom codec
  • Netty what codecs are available out of the box

1. Learn codecs, starting with sticky/unpack

1.1 Why is there sticking/unpacking

Sticky/unpack problem, I believe we have all heard, this problem mainly includes three reasons:

1) MTU and MSS restrictions

Maxitum Transmission Unit (MTU) is the maximum number of bytes that can be sent at a time at the data link layer in the OSI five-layer network model. Generally, the MTU size is 1500 bytes.

Maximum sesize (MSS) refers to the Maximum length of data in THE TCP packets, which is the Maximum Size of data that the transport layer can send ata time.

The relationship between MSS and MTU is as follows:

MSS length =MTU length -IP header-TCP Header

Therefore, if the MSS length + IP Header + TCP Header > MTU length, multiple packets need to be split and sent, resulting in packet unpacking.

2) TCP sliding window TCP flow control method is “sliding window”. When A sends data to B, B, as the receiver, will inform the sender of the window value acceptable to A, so as to control the amount of traffic sent by A and achieve the purpose of traffic control.

Suppose the receiver B tells the sender that the window size of A is 256 bytes, which means that the sender can send up to 256 bytes. Since the data size of the sender is 518 bytes, only the first 256 bytes can be sent and the remaining bytes can be sent after the receiver ack. This can lead to “unpacking”.

3) Nagle algorithm

In THE TCP/IP protocol, a protocol Header (TCP Header + IP Header) is added before DATA, regardless of the size of DATA to be sent. If only 1 byte of data needs to be sent at a time, plus 20 byte IP headers and 20 byte TCP headers, the packet size is 41 bytes at a time, but only 1 byte of information is actually valid, which is a huge waste.

Therefore, THE Nagle algorithm is used in TCP/IP to improve efficiency.

The core idea of Nagle algorithm lies in “merging parts into a whole”. It writes data to the buffer before it is acknowledged, and waits for the data to be acknowledged or the buffer to grow to a certain size before sending the packet.

Sticky packets are caused when multiple small packets are combined and sent together.

Q: If Nagle is disabled, do you still need to handle sticky packets? A: need. In addition to Nagle algorithm, the receiver is not timely may also cause sticky packet phenomenon. When the previous packet is still in the buffer and not processed by the receiver, the next packet has arrived, and then the receiver may fetch multiple packets according to the size of the buffer.

1.2 How to Handle Sticking/Unpacking

For TCP, in fact, we all know that one of its characteristics is the “byte stream oriented” transmission protocol, itself does not have the boundary of packets. So whatever the cause of sticky/unpack, TCP itself is reliable and correct.

Let’s be clear: sticky/unpack problems are essentially data parsing problems at the application layer.

Therefore, the core method to solve the unpacking/sticky packet problem is to define the communication protocol of the application layer.

The key is to define the right data boundaries.

There are three common protocol solutions:

1) Fixed length

Each data message has a fixed length.

When the receiver accumulatively reads fixed-length packets, it considers that it has obtained a complete message.

For example, if we want to send an ABCDEFGHIJKLM message with a fixed length of 4, then the receiver can parse the message with a length of 4. As shown below.

ABCD

EFGH

IJKL

MN00

When the data of the sender is less than a fixed length, such as the last packet, which only contains two characters of MN, vacancy completion is required.

This scheme is very simple, but the disadvantages are also very obvious, very inflexible. If the fixed-length definition is too long, data transfer space is wasted. If the definition is too short, proper data transfer can be compromised. This method is not generally adopted.

2) Specific delimiters

In addition to fixed lengths, the easiest way to distinguish “data boundaries” is to use “specific separators.” When the receiver reads a specific delimiter, it is considered to have received a complete message.

For example, we use the newline character \n to distinguish.

AB\nCDEFG\nHIJK\nLMN\n
Copy the code

This method is more flexible and accommodates messages of different lengths. However, care must be taken that the “special delimiter” cannot duplicate the message content, or parsing will fail.

Therefore, in practice, we can consider encoding messages (such as Base64) and using symbols outside the coded character set as “specific separators.”

This scheme is generally used in scenarios with simple protocols.

3) Message length + content In general project development, the most common way is to use message length + content for processing. For example, define a message format like this:

Message length (such as 4-byte length storage)

The message content

3

ABC

Stored in such a format, the recipient of a message reads a 4-byte message as the “message length” when parsed. This is 3, indicating a 3-byte message length. Three bytes of message content are then read as the complete message.

Here’s an example:

2AB5CDEFG4HIJK3LMN
Copy the code

The message length + content approach is very flexible and can be applied to a variety of scenarios.

Note that in the message header, in addition to defining the length of the message, you can also customize other extension fields, such as message version, algorithm type, and so on.

2. How to implement a custom codec in Netty

Above we have seen the causes of “sticky/unpack” and common solutions. Here’s how to implement a custom codec in Netty.

Netty, as an excellent network communication framework, has provided a very rich abstract class to deal with codec. We only need to customize the codec algorithm extension.

2.1 Customizing encoders

Let’s look at custom encoders first. Because the encoder is relatively simple, there is no need to pay attention to the “sticky/unpack problem”.

Commonly used code abstract classes including MessageToByteEncoder and MessageToMessageEncoder, inherited from ChannelOutboundHandlerAdapter, operating Outbound data.

1) MessageToByteEncoder This encoder is used to encode message objects into byte streams. It provides an abstract method called encode, so we just need to implement encode, and we can do custom coding.

The encoder implementation is very simple and does not need to worry about unpacking/sticking.

Here’s an example of converting a String message to a byte stream:

2) MessageToMessageEncoder This encoder is used to encode one message object into another. The second Message here can be interpreted as any object. If you use the ByteBuf object, this is the same as MessageToByteEncoder above.

StringEncoder:

2.2 Custom decoder

Decoders are a little more complicated than encoders because of the “unpacking/sticky packing” problem to consider.

Since the receiver may not have received the complete message, the decoding framework needs to buffer the inbound data until the complete message is retrieved.

Commonly used decoder abstract classes including ByteToMessageDecoder and MessageToMessageDecoder, inherited from ChannelInboundHandlerAdapter, is Inbbound related data of the operation.

General general practice is to use ByteToMessageDecoder to resolve TCP protocol, to solve the problem of unpacking/sticky packet. Valid ByteBuf data is obtained by parsing, and then passed to the subsequent MessageToMessageDecoder to convert data objects.

1) ByteToMessageDecoder The ByteToMessageDecoder is used to decode byte streams into message objects.

Take the “fixed length method” above to solve the “sticky/unpack” example, Netty’s own FixedLengthFrameDecoder.

The message is parsed with a fixed-length frameLength.

In production practice, custom codecs may be implemented using more complex protocols, such as Protobuf.

MessageToMessageDecoder A decoder is used to decode one message object into another. If you need to convert parsed byte data to the object model, you will need this decoder.

3.Net TY What decoders are available out of the box

As an excellent network programming framework, Netty provides a very rich set of out-of-the-box codecs in addition to supporting extended custom codecs. In particular, the three solutions to the sticky/unpack problem mentioned in section 1.2 above are implemented out of the box.

3.1 FixedLengthFrameDecoder

This decoder was mentioned above and corresponds to the “fixed length decoder” in Section 1.2, which is expanded slightly here.

The fixed-length frameLength is configured through the constructor, and then decoded as frameLength when decode is performed.

  • When a frameLength message is read, the decoder thinks it has got a complete message.
  • When the message length is less than frameLength, the FixedLengthFrameDecoder decoder waits for subsequent packets to arrive until the complete message is obtained.

3.2 special delimiter decoder DelimiterBasedFrameDecoder

This decoder corresponds to the “Special Separator Decoder” in Section 1.2 and is also a decoder inherited from ByteToMessageDecoder.

The decoder uses one or more delimiter symbols to decode the incoming message (ByteBuf).

Let’s take a look at the constructor and look at a few important parameters.

  • maxFranmeLength

MaxFranmeLength is the maximum length limit for messages to be processed. TooLongFrameException will be thrown if maxFranmeLength exceeds and the specified delimiter has not been detected.

  • stripDelimiter

StripDelimiter is a Boolean type that determines whether the delimiter is removed from the decoded message. If stripDelimiter=false, the decoded message content preserves the delimiter information.

  • failFast

FailFast is a Boolean type. If true, a TooLongFrameException will be thrown immediately after the message exceeds maxFranmeLength. If false, a TooLongFrameException will not be thrown until a complete message has been decoded.

  • delimiters

Delimiters are of type ByteBuf array, and you can pass in more than one delimiter at the same time in the constructor, but when parsing, you end up choosing the one with the shortest length for message splitting.

For example, the received data is:

ABCD\nEFG\r\n 
Copy the code

If the delimiters specified are \n and \r\n, two messages are decoded.

ABCD   EFG
Copy the code

If the specific delimiter specified is only \r\n, only one message will be decoded:

ABCD\nEFG 
Copy the code

Decoder LengthFieldBasedFrameDecoder 3.3 a length field

This decoder, one of the most widely used in production practices (such as RocketMQ), is relatively complex, but extremely flexible, and can basically cover a variety of length-based unpacking schemes, such as the “message length + content” scheme mentioned in Section 1.2.

When using this decoder, the key need to understand the four parameters, master the parameter Settings, can quickly achieve different length based unpacking decoding scheme.

Parameter names

type

meaning

lengthFieldOffset

int

The offset of the length field. Indicates the start position of the Length field

lengthFieldLength

int

Length Number of bytes occupied by a field

lengthAdjustment

int

The modified value of the message length. Indicates that in some complex protocols, other content, such as the version number and message type, will be added to the length field, which requires correction

initialBytesToStrip

int

Initial number of bytes to skip after decoding. Represents the starting location of message content data

1) Decoding scheme 1: Based on message length + message content, the decoding result does not truncate the message header

The packet contains only the message Length and Content fields. The Length field is in hexadecimal format and occupies 2 bytes. The Length value 0x000C indicates that the Content field occupies 12 bytes.

Parameter names

The values

lengthFieldOffset

0

lengthFieldLength

2

lengthAdjustment

0

initialBytesToStrip

0 (indicates that the decoding result does not truncate the header)

Decoding examples:

2) Decoding scheme 2: Truncate the decoding result based on message length + message content

Unlike scheme 1, the decoding result truncates the message header (skipping 2 bytes)

Parameter names

The values

lengthFieldOffset

0

lengthFieldLength

2

lengthAdjustment

0

initialBytesToStrip

2 (indicates the Length of bytes skipped from the Length field and only contains the message content after decoding)

Decoding examples:

3) Decoding scheme 3: Based on message header + message length + message content

A special header is added at the start of the message, and the Length field is moved later.

Parameter names

The values

lengthFieldOffset

2

lengthFieldLength

3

lengthAdjustment

0

initialBytesToStrip

0 (indicates that the decoding result does not truncate the header)

Decoding examples:

4) Decoding scheme 4: Based on message length + message header + message content

The start position of the message is the Length field of the message. Instead of adding the message content directly, you add the message header first and then the message content.

Parameter names

The values

lengthFieldOffset

0

lengthFieldLength

3

lengthAdjustment

2 (length of Header1)

initialBytesToStrip

0 (indicates that the decoding result does not truncate the header)

Decoding examples:

Since Length is not immediately followed by content, lengthAdjustment (2 bytes) is required to get the content of Header + Content (14 bytes).

4. Summary

Just a quick review.

This paper mainly introduces a typical application scenario of ChannelHandler – codec.

Codecs focus on the processing of “sticky/unpack”, we introduce the causes of “sticky/unpack” and common solutions. It then explains how to use the Netty framework to implement custom codecs.

Finally, several codecs that are very useful out of the box in Netty are introduced.

Bibliography: Netty in Action

See the end, the original is not easy, point a concern, point a like it ~

Reorganize the knowledge fragments and construct the Java knowledge graph: github.com/saigu/JavaK… (Easy access to historical articles)