大多數(shù)的協(xié)議(私有或者公有)不脯,協(xié)議頭中會攜帶長度字段,用于標識消息體或者整包消息的長度酣倾,例如SMPP、HTTP協(xié)議等谤专。由于基于長度解碼需求 的通用性灶挟,Netty提供了LengthFieldBasedFrameDecoder
/LengthFieldPrepender
,自動屏蔽TCP底層的拆包和粘包問題毒租,只需要傳入正確的參數(shù),即可輕松解決“讀半包“問題箱叁。
發(fā)送方使用LengthFieldPrepender給實際內(nèi)容Content進行編碼添加報文頭Length字段墅垮,接受方使用LengthFieldBasedFrameDecoder進行解碼。協(xié)議格式如下所示:
+--------+----------+
| Length | Content |
+--------+----------+
Length字段:
表示Conent部分的字節(jié)數(shù)耕漱,例如Length值為100算色,那么意味著Conent部分占用的字節(jié)數(shù)就是100。
Length字段本身是個整數(shù)螟够,也要占用字節(jié)灾梦,一般會使用固定的字節(jié)數(shù)表示。例如我們指定使用2個字節(jié)(有符號)表示length妓笙,那么可以表示的最大值為32767(約等于32K)若河,也就是說,Content部分占用的字節(jié)數(shù)寞宫,最大不能超過32767萧福。當然,Length字段存儲的是Content字段的真實長度辈赋。
Content字段:
是我們要處理的真實二進制數(shù)據(jù)鲫忍。 在發(fā)送Content內(nèi)容之前,首先需要獲取其真實長度钥屈,添加在內(nèi)容二進制流之前悟民,然后再發(fā)送。Length占用的字節(jié)數(shù)+Content占用的字節(jié)數(shù)篷就,就是我們總共要發(fā)送的字節(jié)射亏。
事實上,我們可以把Length部分看做報文頭腻脏,報文頭包含了解析報文體(Content字段)的相關(guān)元數(shù)據(jù)鸦泳,例如Length報文頭表示的元數(shù)據(jù)就是Content部分占用的字節(jié)數(shù)。當然永品,LengthFieldBasedFrameDecoder并沒有限制我們只能添加Length報文頭做鹰,我們可以在Length字段前或后,加上一些其他的報文頭鼎姐,此時協(xié)議格式如下所示:
+---------+--------+----------+----------+
|........ | Length | ....... | Content |
+---------+--------+----------+----------+
不過對于LengthFieldBasedFrameDecoder而言钾麸,其關(guān)心的只是Length字段更振。因此當我們在構(gòu)造一個LengthFieldBasedFrameDecoder時,最主要的就是告訴其如何處理Length字段饭尝。
2 LengthFieldPrepender參數(shù)詳解
LengthFieldPrepender提供了多個構(gòu)造方法肯腕,最終調(diào)用的都是:
public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
}
public LengthFieldPrepender(
ByteOrder byteOrder, int lengthFieldLength,
int lengthAdjustment, boolean lengthIncludesLengthFieldLength)
其中:
byteOrder:表示Length字段本身占用的字節(jié)數(shù)使用的是大端還是小端編碼
lengthFieldLength:表示Length字段本身占用的字節(jié)數(shù),只可以指定 1, 2, 3, 4, 或 8
lengthAdjustment:表示Length字段調(diào)整值
lengthIncludesLengthFieldLength:表示Length字段本身占用的字節(jié)數(shù)是否包含在Length字段表示的值中。
例如:對于以下包含12個字節(jié)的報文
+----------------+
| "HELLO, WORLD" |
+----------------+
假設(shè)我們指定Length字段占用2個字節(jié)钥平,lengthIncludesLengthFieldLength指定為false实撒,即不包含本身占用的字節(jié),那么Length字段的值為0x000C(即12)涉瘾。
+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
如果我們指定lengthIncludesLengthFieldLength指定為true知态,那么Length字段的值為:0x000E(即14)=Length(2)+Content字段(12)
+--------+----------------+
+ 0x000E | "HELLO, WORLD" |
+--------+----------------+
關(guān)于lengthAdjustment字段的含義,參見下面的LengthFieldBasedFrameDecoder立叛。
LengthFieldPrepender尤其值得說明的一點是负敏,其提供了實現(xiàn)零拷貝的另一種思路(實際上編碼過程,是零拷貝的一個重要應(yīng)用場景
)秘蛇。
在Netty中我們可以使用ByteBufAllocator.directBuffer()創(chuàng)建直接緩沖區(qū)實例其做,從而避免數(shù)據(jù)從堆內(nèi)存(用戶空間)向直接內(nèi)存(內(nèi)核空間)的拷貝,這是系統(tǒng)層面的零拷貝赁还;
也可以使用
CompositeByteBuf
把兩個ByteBuf合并在一起妖泄,例如一個存放報文頭,另一個存放報文體艘策。而不是創(chuàng)建一個更大的ByteBuf浮庐,把兩個小ByteBuf合并在一起,這是應(yīng)用層面的零拷貝柬焕。
而LengthFieldPrepender审残,由于需要在原來的二進制數(shù)據(jù)之前添加一個Length字段,因此就需要對二者進行合并發(fā)送斑举。但是LengthFieldPrepender并沒有采用CompositeByteBuf搅轿,其編碼過程如下:
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//1 獲得Length字段的值:真實數(shù)據(jù)可讀字節(jié)數(shù)+Length字段調(diào)整值
int length = msg.readableBytes() + lengthAdjustment;
if (lengthIncludesLengthFieldLength) {
length += lengthFieldLength;
}
...
//2 根據(jù)lengthFieldLength指定的值(1、2富玷、3璧坟、4、8)赎懦,創(chuàng)建一個ByteBuffer實例雀鹃,寫入length的值,
//并添加到List類型的out變量中
switch (lengthFieldLength) {
case 1:
if (length >= 256) {
throw new IllegalArgumentException(
"length does not fit into a byte: " + length);
}
out.add(ctx.alloc().buffer(1).order(byteOrder).writeByte((byte) length));
break;
...
case 8:
out.add(ctx.alloc().buffer(8).order(byteOrder).writeLong(length));
break;
default:
throw new Error("should not reach here");
}
//3 最后励两,再將msg本身添加到List中(msg.retain是增加一次引用黎茎,返回的還是msg本身)
out.add(msg.retain());
}
}
可以看到,LengthFieldPrepender實際上是先把Length字段(報文頭)添加到List中当悔,再把msg本身(報文)添加到List中傅瞻。而在發(fā)送數(shù)據(jù)時踢代,LengthFieldPrepender的父類MessageToMessageEncoder會按照List中的元素下標按照順序發(fā)送,因此相當于間接的把Length字段添加到了msg之前嗅骄。從而避免了創(chuàng)建一個更大的ByteBuf將Length字段和msg內(nèi)容合并到一起胳挎。作為開發(fā)者的我們,在編寫編碼器的時候溺森,這種一種重要的實現(xiàn)零拷貝的參考思路慕爬。
3 LengthFieldBasedFrameDecoder參數(shù)詳解
LengthFieldBasedFrameDecoder提供了多個構(gòu)造方法,最終調(diào)用的都是:
public LengthFieldBasedFrameDecoder(
int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
this( ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip, failFast);
}
public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip, boolean failFast)
其中:
byteOrder:
表示協(xié)議中Length字段的字節(jié)是大端還是小端
maxFrameLength:
表示協(xié)議中Content字段的最大長度屏积,如果超出澡罚,則拋出TooLongFrameException異常肾请。
lengthFieldOffset:
表示Length字段的偏移量,即在讀取一個二進制流時更胖,跳過指定長度個字節(jié)之后的才是Length字段铛铁。如果Length字段之前沒有其他報文頭,指定為0即可却妨。如果Length字段之前還有其他報文頭饵逐,則需要跳過之前的報文頭的字節(jié)數(shù)。
lengthFieldLength:
表示Length字段占用的字節(jié)數(shù)彪标。指定為多少倍权,需要看實際要求,不同的字節(jié)數(shù)捞烟,限制了Content字段的最大長度薄声。
- 如果lengthFieldLength是1個字節(jié),那么限制為128bytes题画;
- 如果lengthFieldLength是2個字節(jié)默辨,那么限制為32767(約等于32K);
- 如果lengthFieldLength是3個字節(jié)苍息,那么限制為8388608(約等于8M)缩幸;
- 如果lengthFieldLength是4個字節(jié),那么限制為2147483648(約等于2G)竞思。
lengthFieldLength與maxFrameLength并不沖突表谊。例如我們現(xiàn)在希望限制報文Content字段的最大長度為32M。顯然盖喷,我們看到了上面的四種情況爆办,沒有任何一個值,能剛好限制Content字段最大值剛好為32M课梳。那么我們只能指定lengthFieldLength為4個字節(jié)押逼,其最大限制2G是大于32M的步藕,因此肯定能支持。但是如果Content字段長度真的是2G挑格,server端接收到這么大的數(shù)據(jù)咙冗,如果都放在內(nèi)存中,很容易造成內(nèi)存溢出漂彤。
為了避免這種情況雾消,我們就可以指定maxFrameLength字段,來精確的指定Content部分最大字節(jié)數(shù)挫望,顯然立润,其值應(yīng)該小于lengthFieldLength指定的字節(jié)數(shù)最大可以表示的值。
lengthAdjustment:
Length字段補償值媳板。對于絕大部分協(xié)議來說桑腮,Length字段的值表示的都是Content字段占用的字節(jié)數(shù)。但是也有一些協(xié)議蛉幸,Length字段表示的是Length字段本身占用的字節(jié)數(shù)+Content字段占用的字節(jié)數(shù)破讨。由于Netty中在解析Length字段的值是,默認是認為其只表示Content字段的長度奕纫,因此解析可能會失敗提陶,所以要進行補償。在后面的案例3中進行了演示匹层。
主要用于處理Length字段前后還有其他報文頭的情況隙笆。具體作用請看后面的案例分析。
initialBytesToStrip:
解碼后跳過的初始字節(jié)數(shù)升筏,表示獲取完一個完整的數(shù)據(jù)報文之后撑柔,忽略前面指定個數(shù)的字節(jié)。例如報文頭只有Length字段您访,占用2個字節(jié)乏冀,在解碼后,我們可以指定跳過2個字節(jié)洋只。這樣封裝到ByteBuf中的內(nèi)容辆沦,就只包含Content字段的字節(jié)內(nèi)容不包含Length字段占用的字節(jié)。
failFast:
如果為true识虚,則表示讀取到Length字段時肢扯,如果其值超過maxFrameLength,就立馬拋出一個 TooLongFrameException担锤,而為false表示只有當真正讀取完長度域的值表示的字節(jié)之后蔚晨,才會拋出 TooLongFrameException,默認情況下設(shè)置為true,建議不要修改铭腕,否則可能會造成內(nèi)存溢出银择。
下面通過再幾個案例,來說明這些參數(shù)是如何控制LengthFieldBasedFrameDecoder解碼行為的累舷,首先我們討論報文只包含Length字段和Content字段的情況浩考;接著討論報文頭除了包含Length字段,還有其他報文頭字段的情況被盈。
3.1 報文只包含Length字段和Content字段
報文只包含Length字段和Content字段時析孽,協(xié)議格式如下:
+--------+----------+
| Length | Content |
+--------+----------+
假設(shè)Length字段占用2個字節(jié),其值為0x000C只怎,意味著Content字段長度為12個字節(jié)袜瞬,假設(shè)其內(nèi)容為”HELLO, WORLD”。下面演示指定不同解析參數(shù)時身堡,解碼后的效果邓尤。
案例1:
lengthFieldOffset = 0 //因為報文以Length字段開始,不需要跳過任何字節(jié)贴谎,所以offset為0
lengthFieldLength = 2 //因為我們規(guī)定Length字段占用字節(jié)數(shù)為2汞扎,所以這個字段值傳入的是2
lengthAdjustment = 0 //這里Length字段值不需要補償,因此設(shè)置為0
initialBytesToStrip = 0 //不跳過初始字節(jié)赴精,意味著解碼后的ByteBuf中,包含Length+Content所有內(nèi)容
解碼前 (14 bytes) 解碼后 (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
案例2:
lengthFieldOffset = 0 //參見案例1
lengthFieldLength = 2 //參見案例1
lengthAdjustment = 0 //參見案例1
initialBytesToStrip = 2 //這里跳過2個初始字節(jié)绞幌,也就是Length字段占用的字節(jié)數(shù)蕾哟,意味著解碼后的ByteBuf中,只包含Content字段
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
案例3:
lengthFieldOffset = 0 // 參見案例1
lengthFieldLength = 2 // 參見案例1
lengthAdjustment = -2 // Length字段補償值指定為-2
initialBytesToStrip = 0 // 參見案例1
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
這個案例需要進行一下特殊說明莲蜘,其Length字段值表示:Length字段本身占用的字節(jié)數(shù)+Content字節(jié)數(shù)谭确。所以我們看到解碼前,其值為0x000E(14)票渠,而不是0x000C(12)逐哈。而真實Content字段內(nèi)容只有2個字節(jié),因此我們需要用:Length字段值0x000E(14)问顷,減去lengthAdjustment指定的值(-2)昂秃,表示的才是Content字段真實長度。
3.2 報文頭包含Length字段以外的其他字段杜窄,同時包含Content字段
通常情況下肠骆,一個協(xié)議的報文頭除了Length字段,還會包含一些其他字段塞耕,例如協(xié)議的版本號蚀腿,采用的序列化協(xié)議,是否進行了壓縮扫外,甚至還會包含一些預(yù)留的頭字段莉钙,以便未來擴展廓脆。這些字段可能位于Length之前,也可能位于Length之后磁玉,此時的報文協(xié)議格式如下所示:
+---------+--------+----------+----------+
|........ | Length | ....... | Content |
+---------+--------+----------+----------+
當然停忿,對于LengthFieldBasedFrameDecoder來說,其只關(guān)心Length字段蜀涨。按照Length字段的值解析出一個完整的報文放入ByteBuf中瞎嬉,也就是說,LengthFieldBasedFrameDecoder只負責粘包厚柳、半包的處理氧枣,而ByteBuf中的實際內(nèi)容解析,則交由后續(xù)的解碼器進行處理别垮。
下面依然通過案例進行說明:
案例4:
這個案例中便监,在Length字段之前,還包含了一個Header字段碳想,其占用2個字節(jié)烧董,Length字段占用3個字節(jié)。
lengthFieldOffset = 2 // 需要跳過Header字段占用的2個字節(jié)胧奔,才是Length字段
lengthFieldLength = 3 //Length字段占用3個字節(jié)
lengthAdjustment = 0 //由于Length字段的值為12逊移,表示的是Content字段長度,因此不需要調(diào)整
initialBytesToStrip = 0 //解碼后龙填,不裁剪字節(jié)
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Header | Length | Actual Content |----->| Header | Length | Actual Content |
| 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
案例5:
在這個案例中胳泉,Header字段位于Length字段之后
lengthFieldOffset = 0 // 由于一開始就是Length字段,因此不需要跳過
lengthFieldLength = 3 // Length字段占用3個字節(jié)岩遗,其值為0x000C扇商,表示Content字段長度
lengthAdjustment = 2 // 由于Length字段之后,還有Header字段宿礁,因此需要+2個字節(jié)案铺,讀取Header+Content的內(nèi)容
initialBytesToStrip = 0 //解碼后,不裁剪字節(jié)
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Length | Header | Actual Content |----->| Length | Header | Actual Content |
| 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
案例6 :
這個案例中梆靖,Length字段前后各有一個報文頭字段HDR1控汉、HDR2,各占1個字節(jié)
lengthFieldOffset = 1 //跳過HDR1占用的1個字節(jié)讀取Length
lengthFieldLength = 2 //Length字段占用2個字段返吻,其值為0x000C(12)暇番,表示Content字段長度
lengthAdjustment = 1 //由于Length字段之后,還有HDR2字段思喊,因此需要+1個字節(jié)壁酬,讀取HDR2+Content的內(nèi)容
initialBytesToStrip = 3 //解碼后,跳過前3個字節(jié)
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
案例7:
//這個案例中,Length字段前后各有一個報文頭字段HDR1舆乔、HDR2岳服,各占1個字節(jié)。Length占用2個字節(jié)希俩,表示的是整個報文的總長度吊宋。
lengthFieldOffset = 1 //跳過HDR1占用的1個字節(jié)讀取Length
lengthFieldLength = 2 //Length字段占用2個字段,其值為0x0010(16)颜武,表示HDR1+Length+HDR2+Content長度
lengthAdjustment = -3 //由于Length表示的是整個報文的長度璃搜,減去HDR1+Length占用的3個字節(jié)后,讀取HDR2+Content長度
initialBytesToStrip = 3 //解碼后鳞上,跳過前3個字節(jié)
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
4 代碼案例
了解了LengthFieldPrepender和LengthFieldBasedFrameDecoder的作用后这吻,我們編寫一個實際的案例。client發(fā)送一個請求篙议,使用LengthFieldPrepender進行編碼唾糯,server接受請求,使用LengthFieldBasedFrameDecoder進行解碼鬼贱。
我們采用最簡單的的通信協(xié)議格式移怯,不指定其他報文頭:
+--------+-----------+
| Length | Content |
+--------+-----------+
Client端代碼
public class LengthFieldBasedFrameDecoderClient {
public static void main(String[] args) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap(); // (1)
b.group(workerGroup); // (2)
b.channel(NioSocketChannel.class); // (3)
b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldPrepender(2,0,false));
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// 在于server建立連接后,即發(fā)送請求報文
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush("i am request!");
ctx.writeAndFlush("i am a anther request!");
}
});
}
});
// Start the client.
ChannelFuture f = b.connect("127.0.0.1", 8080).sync(); // (5)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
在Client端我們自定義了一個ChannelInboundHandler这难,在連接被激活時舟误,立即發(fā)送兩個請求:"i am request!”、"i am a anther request!" 姻乓。另外注意嵌溢,我們是分兩次調(diào)用ctx.writeAndFlush,每次調(diào)用都會導(dǎo)致當前請求數(shù)據(jù)經(jīng)過StringEncoder進行編碼糖权,得到包含這個請求內(nèi)容ByteBuf實例堵腹, 然后再到LengthFieldPrepender進行編碼添加Length字段炸站。
因此我們發(fā)送的實際上是以下2個報文
Length(13) Content
+--------+-------------------+
+ 0x000D | "i am request!" |
+--------+-------------------+
Length(23) Content
+--------+-----------------------------+
+ 0x0017 | "i am a anther request!" |
+--------+-----------------------------+
Server端代碼代碼
public class LengthFieldBasedFrameDecoderServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(16384, 0, 2, 0, 2));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("receive req:"+msg);
}
});
}
});
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(8080).sync(); // (7)
System.out.println("LengthFieldBasedFrameDecoderServer Started on 8080...");
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
在Server端星澳,我們通過LengthFieldBasedFrameDecoder進行解碼,并刪除Length字段的2個字節(jié)旱易,交給之后StringDecoder轉(zhuǎn)換為字符串禁偎,最后在我們的自定義的ChannelInboundHandler進行打印。
分別啟動server端與client端阀坏,在server端如暖,我們將看到輸出:
LengthFieldBasedFrameDecoderServer Started on 8080...
receive req:i am request!
receive req:i am a anther request!
注意這里打印了2次,表示LengthFieldBasedFrameDecoder的確解碼成功忌堂。
部分讀者可能不信任這個結(jié)果盒至,那么可以嘗試將Server端的LengthFieldBasedFrameDecoder和Client端的LengthFieldPrepender注釋掉,再次運行的話,大概率你在server端控制臺將看到:
LengthFieldBasedFrameDecoderServer Started on 8080...
receive req:i am request!i am a anther request!
也就是說枷遂,在沒有使用LengthFieldBasedFrameDecoder和LengthFieldPrepender的情況下樱衷,發(fā)生了粘包,而服務(wù)端無法區(qū)分酒唉。
5 總結(jié)
LengthFieldBasedFrameDecoder作用實際上只是幫我們處理粘包和半包的問題矩桂,其只負責將可以構(gòu)成一個完整有效的請求報文封裝到ByteBuf中,之后還要依靠其他的解碼器對報文的內(nèi)容進行解析痪伦,例如上面編寫的String將其解析為字符串侄榴,只不過在后續(xù)的解碼器中,不需要處理粘包半包問題了网沾,認為ByteBuf中包含的內(nèi)容肯定是一個完整的報文即可癞蚕。
對于請求和響應(yīng)都是字符串的情況下,LengthFieldBasedFrameDecoder/LengthFieldPrepender的威力還沒有完全展示出來绅这。我們甚至可以將自定義的POJO類作為請求/響應(yīng)涣达,在發(fā)送數(shù)據(jù)前對其序列化字節(jié)數(shù)組,然后通過LengthFieldPrepender為其制定Length证薇;服務(wù)端根據(jù)Length解析得到二進制字節(jié)流度苔,然后反序列化再得到POJO類實例。在下一節(jié)我們將會詳細的進行介紹浑度。