A specialized variation of ByteToMessageDecoder which enables implementation of a non-blocking decoder in the blocking I/O paradigm.
The biggest difference between ReplayingDecoder and ByteToMessageDecoder is that ReplayingDecoder allows you to implement the decode() and decodeLast() methods just like all required bytes were received already, rather than checking the availability of the required bytes. For example, the following ByteToMessageDecoder implementation:
一个特殊的ByteToMessageDecoder ,可以在阻塞的i/o模式下实现非阻塞的解码。
ReplayingDecoder 和ByteToMessageDecoder 最大的不同就是ReplayingDecoder 允许你实现decode()和decodeLast()就像所有的字节已经接收到一样,不需要判断可用的字节,举例,下面的ByteToMessageDecoder 实现:
How does this work?
ReplayingDecoder passes a specialized ByteBuf implementation which throws an Error of certain type when there's not enough data in the buffer. In the IntegerHeaderFrameDecoder above, you just assumed that there will be 4 or more bytes in the buffer when you call buf.readInt(). If there's really 4 bytes in the buffer, it will return the integer header as you expected. Otherwise, the Error will be raised and the control will be returned to ReplayingDecoder. If ReplayingDecoder catches the Error, then it will rewind the readerIndex of the buffer back to the 'initial' position (i.e. the beginning of the buffer) and call the decode(..) method again when more data is received into the buffer.
Please note that ReplayingDecoder always throws the same cached Error instance to avoid the overhead of creating a new Error and filling its stack trace for every throw.
Limitations
At the cost of the simplicity, ReplayingDecoder enforces you a few limitations:
Some buffer operations are prohibited.
Performance can be worse if the network is slow and the message format is complicated unlike the example above. In this case, your decoder might have to decode the same part of the message over and over again.
You must keep in mind that decode(..) method can be called many times to decode a single message. For example, the following code will not work:
限制
简化使用带来的成本,ReplayingDecoder 强制带来了2个限制:
// A message contains 2 integers. values.offer(buf.readInt()); values.offer(buf.readInt());
// This assertion will fail intermittently since values.offer() // can be called more than two times! assert values.size() == 2; out.add(values.poll() + values.poll()); } }
The correct implementation looks like the following, and you can also utilize the ‘checkpoint’ feature which is explained in detail in the next section. 正确的实现应该是下边这样的方式
// Revert the state of the variable that might have been changed // since the last partial decode. values.clear();//首先要清理掉里边的消息
// A message contains 2 integers. values.offer(buf.readInt()); values.offer(buf.readInt());
// Now we know this assertion will never fail. assert values.size() == 2; out.add(values.poll() + values.poll()); } }
Improving the performance
性能提升
Fortunately, the performance of a complex decoder implementation can be improved significantly with the checkpoint() method. The checkpoint() method updates the 'initial' position of the buffer so that ReplayingDecoder rewinds the readerIndex of the buffer to the last position where you called the checkpoint() method.
幸好,复杂解码器性能的提升可以通过checkpoint()方法实现,checkpoint()方法可以更新buffer的初始化的位置,这样ReplayingDecoder 就可以在调用checkpoint()方法的时候重新回到上一次读索引的位置。
Calling checkpoint(T) with an Enum
Although you can just use checkpoint() method and manage the state of the decoder by yourself, the easiest way to manage the state of the decoder is to create an Enum type which represents the current state of the decoder and to call checkpoint(T) method whenever the state changes. You can have as many states as you want depending on the complexity of the message you want to decode:
通过枚举调用索引,尽管你可以自己使用checkpoint()方法来管理decoder的状态,最易用的方式就是使用枚举来管理decoder的状态,这个枚举代表了当前decoder的状态,当状态改变时可以调用checkpoint()方法,你可以有很多状态取决于你想解码的消息的复杂度。
Replacing a decoder with another decoder in a pipeline 在管道中用另外一个decoder替换一个decoder
If you are going to write a protocol multiplexer, you will probably want to replace a ReplayingDecoder (protocol detector) with another ReplayingDecoder, ByteToMessageDecoder or MessageToMessageDecoder (actual protocol decoder). It is not possible to achieve this simply by calling ChannelPipeline.replace(ChannelHandler, String, ChannelHandler), but some additional steps are required:
public class FirstDecoder extends ReplayingDecoder<Void> {
@Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) { ... // Decode the first message Object firstMessage = ...;
// Add the second decoder ctx.pipeline().addLast("second", new SecondDecoder());
if (buf.isReadable()) { // Hand off the remaining data to the second decoder out.add(firstMessage); out.add(buf.readBytes(super.actualReadableBytes())); } else { // Nothing to hand off out.add(firstMessage); } // Remove the first decoder (me) ctx.pipeline().remove(this); }
/** * A decoder that splits the received {@link ByteBuf}s on line endings. * <p> * Both {@code "\n"} and {@code "\r\n"} are handled. * For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}. * 基于行的解码器,遇到 "\n"、"\r\n"会被作为行分隔符 */ public class LineBasedFrameDecoder extends ByteToMessageDecoder
FixedLengthFrameDecoder
1 2 3 4 5 6 7 8 9 10 11 12 13
/** A decoder that splits the received ByteBufs by the fixed number of bytes. For example, if you received the following four fragmented packets: +---+----+------+----+ | A | BC | DEFG | HI | +---+----+------+----+
A FixedLengthFrameDecoder(3) will decode them into the following three packets with the fixed length: +-----+-----+-----+ | ABC | DEF | GHI | +-----+-----+-----+ 按照固定长度包的解码器 */ public class FixedLengthFrameDecoder extends ByteToMessageDecoder
A decoder that splits the received ByteBufs by one or more delimiters. It is particularly useful for decoding the frames which ends with a delimiter such as NUL or newline characters. Predefined delimiters Delimiters defines frequently used delimiters for convenience' sake.
Specifying more than one delimiter DelimiterBasedFrameDecoder allows you to specify more than one delimiter. If more than one delimiter is found in the buffer, it chooses the delimiter which produces the shortest frame. For example, if you have the following data in the buffer:
a DelimiterBasedFrameDecoder(Delimiters.lineDelimiter()) will choose '\n' as the first delimiter and produce two frames: +-----+-----+ | ABC | DEF | +-----+-----+
rather than incorrectly choosing '\r\n' as the first delimiter: +----------+ | ABC\nDEF | +----------+ 基于分隔符的振解码器,注意使用'\n'分割会产生2个振,使用'\r\n'会产生1个振, DelimiterBasedFrameDecoder原则使用产生最小振的分隔符,即'\n' public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder
LengthFieldBasedFrameDecoder
public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder
A decoder that splits the received ByteBufs dynamically by the value of the length field in the message. It is particularly useful when you decode a binary message which has an integer header field that represents the length of the message body or the whole message.
LengthFieldBasedFrameDecoder has many configuration parameters so that it can decode any message with a length field, which is often seen in proprietary client-server protocols. Here are some example that will give you the basic idea on which option does what.
2 bytes length field at offset 0, do not strip header 偏移量是0不会去除header
The value of the length field in this example is 12 (0x0C) which represents the length of “HELLO, WORLD”. By default, the decoder assumes that the length field represents the number of the bytes that follows the length field. Therefore, it can be decoded with the simplistic parameter combination.
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) +——–+—————-+ +——–+—————-+ | Length | Actual Content |—–>| Length | Actual Content | | 0x000C | “HELLO, WORLD” | | 0x000C | “HELLO, WORLD” | +——–+—————-+ +——–+—————-+ 【Length 的值是12,后边跟随的content的长度是12个字节。】
2 bytes length field at offset 0, strip header
偏移量是0跳过头
Because we can get the length of the content by calling ByteBuf.readableBytes(), you might want to strip the length field by specifying initialBytesToStrip. In this example, we specified 2, that is same with the length of the length field, to strip the first two bytes.
因为我们可以通过调用 ByteBuf.readableBytes()老得到内容的长度,你可以通过指定initialBytesToStrip的值跳过length field,在这例子中,我们指定的是2,他和length field的值是一致的,为了跳过开始的2个字节。 lengthFieldOffset = 0 lengthFieldLength = 2 lengthAdjustment = 0 initialBytesToStrip = 2 (= the length of the Length field)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes) +——–+—————-+ +—————-+ | Length | Actual Content |—–>| Actual Content | | 0x000C | “HELLO, WORLD” | | “HELLO, WORLD” | +——–+—————-+ +—————-+
2 bytes length field at offset 0, do not strip header, the length field represents the length of the whole message
偏移量是0,不跳过头,length field代表是整个消息的长度
In most cases, the length field represents the length of the message body only, as shown in the previous examples. However, in some protocols, the length field represents the length of the whole message, including the message header. In such a case, we specify a non-zero lengthAdjustment. Because the length value in this example message is always greater than the body length by 2, we specify -2 as lengthAdjustment for compensation.
在大多数情况下,length field 代表的是消息体的长度,就像在之前的例子,当然,在某些协议,length field 代表这个消息的长度,在这种情况,我们指定一个非0的lengthAdjustment,因为在这个例子中length value总是大于消息体2,我们指定-2位lengthAdjustment 的值为了补偿。
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2 (= the length of the Length field)
initialBytesToStrip = 0
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) +——–+—————-+ +——–+—————-+ | Length | Actual Content |—–>| Length | Actual Content | | 0x000E | “HELLO, WORLD” | | 0x000E | “HELLO, WORLD” | +——–+—————-+ +——–+—————-+
3 bytes length field at the end of 5 bytes header, do not strip header
The following message is a simple variation of the first example. An extra header value is prepended to the message. lengthAdjustment is zero again because the decoder always takes the length of the prepended data into account during frame length calculation.
lengthFieldOffset = 2 (= the length of Header 1)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0
3 bytes length field at the beginning of 5 bytes header, do not strip header
This is an advanced example that shows the case where there is an extra header between the length field and the message body. You have to specify a positive lengthAdjustment so that the decoder counts the extra header into the frame length calculation.
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2 (= the length of Header 1)
initialBytesToStrip = 0
2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field
This is a combination of all the examples above. There are the prepended header before the length field and the extra header after the length field. The prepended header affects the lengthFieldOffset and the extra header affects the lengthAdjustment. We also specified a non-zero initialBytesToStrip to strip the length field and the prepended header from the frame. If you don't want to strip the prepended header, you could specify 0 for initialBytesToSkip.
lengthFieldOffset = 1 (= the length of HDR1)
lengthFieldLength = 2
lengthAdjustment = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field, the length field represents the length of the whole message
Let's give another twist to the previous example. The only difference from the previous example is that the length field represents the length of the whole message instead of the message body, just like the third example. We have to count the length of HDR1 and Length into lengthAdjustment. Please note that we don't need to take the length of HDR2 into account because the length field already includes the whole header length.
lengthFieldOffset = 1
lengthFieldLength = 2
lengthAdjustment = -3 (= the length of HDR1 + LEN, negative)
initialBytesToStrip = 3