19 變長協(xié)議

大多數(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é)我們將會詳細的進行介紹浑度。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寇窑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子箩张,更是在濱河造成了極大的恐慌甩骏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件先慷,死亡現(xiàn)場離奇詭異艾猜,居然都是意外死亡,警方通過查閱死者的電腦和手機壶冒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門眼刃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脓诡,你說我怎么就攤上這事无午。” “怎么了祝谚?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵宪迟,是天一觀的道長。 經(jīng)常有香客問我交惯,道長次泽,這世上最難降的妖魔是什么穿仪? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮意荤,結(jié)果婚禮上牡借,老公的妹妹穿的比我還像新娘。我一直安慰自己袭异,他們只是感情好钠龙,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著御铃,像睡著了一般碴里。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上上真,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天咬腋,我揣著相機與錄音,去河邊找鬼睡互。 笑死根竿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的就珠。 我是一名探鬼主播寇壳,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妻怎!你這毒婦竟也來了壳炎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逼侦,失蹤者是張志新(化名)和其女友劉穎匿辩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榛丢,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡铲球,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晰赞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稼病。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宾肺,靈堂內(nèi)的尸體忽然破棺而出溯饵,到底是詐尸還是另有隱情侵俗,我是刑警寧澤锨用,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站隘谣,受9級特大地震影響增拥,放射性物質(zhì)發(fā)生泄漏啄巧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一掌栅、第九天 我趴在偏房一處隱蔽的房頂上張望秩仆。 院中可真熱鬧,春花似錦猾封、人聲如沸澄耍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽齐莲。三九已至,卻和暖如春磷箕,著一層夾襖步出監(jiān)牢的瞬間选酗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工岳枷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芒填,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓空繁,卻偏偏與公主長得像殿衰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盛泡,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容