透過現象看本質,我找到了Netty粘包與半包的這幾種解決方案。

来源:https://www.cnblogs.com/jiagooushi/archive/2023/01/05/17027382.html
-Advertisement-
Play Games

1、粘包與半包 啥也不說了,直接上代碼是不是有點不太友好,我所謂了,都快過年了,還要啥自行車 我上來就是一段代碼猛如虎 1.1 伺服器代碼 public class StudyServer { static final Logger log = LoggerFactory.getLogger(Stu ...


1、粘包與半包

啥也不說了,直接上代碼是不是有點不太友好,我所謂了,都快過年了,還要啥自行車

我上來就是一段代碼猛如虎

1.1 伺服器代碼

public class StudyServer {
    static final Logger log = LoggerFactory.getLogger(StudyServer.class);
    void start() {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            // 連接建立時會執行該方法
                            log.debug("connected {}", ctx.channel());
                            super.channelActive(ctx);
                        }

                        @Override
                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                            // 連接斷開時會執行該方法
                            log.debug("disconnect {}", ctx.channel());
                            super.channelInactive(ctx);
                        }
                    });
                }
            });
            ChannelFuture channelFuture = serverBootstrap.bind(8080);
            log.debug("{} binding...", channelFuture.channel());
            channelFuture.sync();
            log.debug("{} bound...", channelFuture.channel());
            // 關閉channel
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            log.debug("stopped");
        }
    }

    public static void main(String[] args) {
        new StudyServer().start();
    }
}

1.2 粘包現象

客戶端代碼

public class StudyClient {
    static final Logger log = LoggerFactory.getLogger(StudyClient.class);
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(worker);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.debug("connected...");
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("sending...");
                            // 每次發送16個位元組的數據,共發送10次
                            for (int i = 0; i < 10; i++) {
                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                ctx.writeAndFlush(buffer);
                            }
                        }
                    });
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}

伺服器接收結果

7999 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x5b43ecb0, L:/127.0.0.1:8080 - R:/127.0.0.1:53797] READ: 160B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+

可見雖然客戶端是分別以 16 位元組為單位,通過 channel 向伺服器發送了 10 次數據,可是 伺服器端卻只接收了一次,接收數據的大小為 160B,即客戶端發送的數據總大小,這就是粘包現象

1.3 半包現象

將客戶端 - 伺服器之間的 channel 容量進行調整

伺服器代碼

// 調整channel的容量
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);

註意

serverBootstrap.option (ChannelOption.SO_RCVBUF, 10) 影響的底層接收緩衝區(即滑動視窗)大小,僅決定了 netty 讀取的最小單位,netty 實際每次讀取的一般是它的整數倍

伺服器接收結果

5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 36B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03                                     |....            |
+--------+-------------------------------------------------+----------------+

5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b                         |........        |
+--------+-------------------------------------------------+----------------+

5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000010| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000020| 0c 0d 0e 0f 00 01 02 03                         |........        |
+--------+-------------------------------------------------+----------------+

5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b                         |........        |
+--------+-------------------------------------------------+----------------+

5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 4B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f                                     |....            |
+--------+-------------------------------------------------+----------------+

可見客戶端每次發送的數據,因 channel 容量不足,無法將發送的數據一次性接收,便產生了半包現象

1.4 現象分析

1.4.1 粘包

  • 現象
    • 發送 abc def,接收 abcdef
  • 原因
    • 應用層
      • 接收方 ByteBuf 設置太大(Netty 預設 1024);接收方緩衝數據,一起發送
    • 傳輸層 - 網路層
      • 滑動視窗:假設發送方 256 bytes 表示一個完整報文,但由於接收方處理不及時且 視窗大小足夠大(大於 256 bytes),這 256 bytes 位元組就會緩衝在接收方的滑動視窗中, 當滑動視窗中緩衝了多個報文就會粘包
      • Nagle 演算法:會造成粘包

1.4.2 半包

  • 現象
    • 發送 abcdef,接收 abc def
  • 原因
    • 應用層

      • 接收方 ByteBuf 小於實際發送數據量
    • 傳輸層 - 網路層

      • 滑動視窗:假設接收方的視窗只剩了 128 bytes,發送方的報文大小是 256 bytes,這時接收方視窗中無法容納發送方的全部報文,發送方只能先發送前 128 bytes,等待 ack 後才能發送剩餘部分,這就造成了半包
    • 數據鏈路層

      • MSS 限制:當發送的數據超過 MSS 限制後,會將數據切分發送,就會造成半包

1.4.3 本質

發生粘包與半包現象的本質是因為 TCP 是流式協議,消息無邊界

1.5 解決方案

1.5.1 短鏈接

客戶端每次向伺服器發送數據以後,就與伺服器斷開連接,此時的消息邊界為連接建立到連接斷開。這時便無需使用滑動視窗等技術來緩衝數據,則不會發生粘包現象。但如果一次性數據發送過多,接收方無法一次性容納所有數據,還是會發生半包現象,所以 短鏈接無法解決半包現象

客戶端代碼改進

修改 channelActive 方法

public void channelActive(ChannelHandlerContext ctx) throws Exception {
    log.debug("sending...");
    ByteBuf buffer = ctx.alloc().buffer(16);
    buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
    ctx.writeAndFlush(buffer);
    // 使用短鏈接,每次發送完畢後就斷開連接
    ctx.channel().close();
}

將發送步驟整體封裝為 send () 方法,調用 10 次 send () 方法,模擬發送 10 次數據

public static void main(String[] args) {
    // 發送10次
    for (int i = 0; i < 10; i++) {
        send();
    }
}

運行結果

6452 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] ACTIVE

6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+

6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 ! R:/127.0.0.1:65024] INACTIVE

6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] ACTIVE

6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+

6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 ! R:/127.0.0.1:65057] INACTIVE

...

客戶端先於伺服器建立連接,此時控制台列印 ACTIVE,之後客戶端向伺服器發送了 16B 的數據,發送後斷開連接,此時控制台列印 INACTIVE,可見 未出現粘包現象

1.5.2 定長解碼器

客戶端於伺服器 約定一個最大長度,保證客戶端每次發送的數據長度都不會大於該長度 。若發送數據長度不足則需要 補齊 至該長度

伺服器接收數據時,將接收到的數據按照約定的最大長度進行拆分,即使發送過程中產生了粘包,也可以通過定長解碼器將數據正確地進行拆分。服務端需要用到 FixedLengthFrameDecoder 對數據進行定長解碼,具體使用方法如下

ch.pipeline().addLast(new FixedLengthFrameDecoder(16));

客戶端代碼

客戶端發送數據的代碼如下

// 約定最大長度為16
final int maxLength = 16;
// 被髮送的數據
char c = 'a';
// 向伺服器發送10個報文
for (int i = 0; i < 10; i++) {
    ByteBuf buffer = ctx.alloc().buffer(maxLength);
    // 定長byte數組,未使用部分會以0進行填充
    byte[] bytes = new byte[maxLength];
    // 生成長度為0~15的數據
    for (int j = 0; j < (int)(Math.random()*(maxLength-1)); j++) {
        bytes[j] = (byte) c;
    }
    buffer.writeBytes(bytes);
    c++;
    // 將數據發送給伺服器
    ctx.writeAndFlush(buffer);
}

伺服器代碼

使用 FixedLengthFrameDecoder 對粘包數據進行拆分,該 handler 需要添加在 LoggingHandler 之前,保證數據被列印時已被拆分

// 通過定長解碼器對粘包數據進行拆分
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));

運行結果

8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 00 00 00 00 00 00 00 00 00 00 00 00 |aaaa............|
+--------+-------------------------------------------------+----------------+

8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 00 00 00 00 00 00 00 00 00 00 00 00 00 |bbb.............|
+--------+-------------------------------------------------+----------------+


8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |cc..............|
+--------+-------------------------------------------------+----------------+

...

1.5.3 行解碼器

行解碼器的是 通過分隔符對數據進行拆分 來解決粘包半包問題的

可以通過 LineBasedFrameDecoder(int maxLength) 來拆分以換行符 (\n) 為分隔符的數據,也可以通過 DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) 來指定通過什麼分隔符來拆分數據(可以傳入多個分隔符)

兩種解碼器 都需要傳入數據的最大長度 ,若超出最大長度,會拋出 TooLongFrameException 異常

以換行符 \n 為分隔符

客戶端代碼

// 約定最大長度為 64
final int maxLength = 64;
// 被髮送的數據
char c = 'a';
for (int i = 0; i < 10; i++) {
    ByteBuf buffer = ctx.alloc().buffer(maxLength);
    // 生成長度為0~62的數據
    Random random = new Random();
    StringBuilder sb = new StringBuilder();
    for (int j = 0; j < (int)(random.nextInt(maxLength-2)); j++) {
        sb.append(c);
    }
    // 數據以 \n 結尾
    sb.append("\n");
    buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
    c++;
    // 將數據發送給伺服器
    ctx.writeAndFlush(buffer);
}

伺服器代碼

// 通過行解碼器對粘包數據進行拆分,以 \n 為分隔符
// 需要指定最大長度
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(64));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));

運行結果

4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61                   |aaaaaaaaaa      |
+--------+-------------------------------------------------+----------------+

4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 11B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 62 62 62 62 62 62 62 62                |bbbbbbbbbbb     |
+--------+-------------------------------------------------+----------------+

4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 2B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63                                           |cc              |
+--------+-------------------------------------------------+----------------+

...

以自定義分隔符 \c 為分隔符

客戶端代碼

...
    
// 數據以 \c 結尾
sb.append("\\c");
buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));

...

伺服器代碼

// 將分隔符放入ByteBuf中
ByteBuf bufSet = ch.alloc().buffer().writeBytes("\\c".getBytes(StandardCharsets.UTF_8));
// 通過行解碼器對粘包數據進行拆分,以 \c 為分隔符
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(64, ch.alloc().buffer().writeBytes(bufSet)));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));

運行結果

8246 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 14B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61 61 61 61 61       |aaaaaaaaaaaaaa  |
+--------+-------------------------------------------------+----------------+


8247 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 3B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62                                        |bbb             |
+--------+-------------------------------------------------+----------------+

...

1.5.4 長度欄位解碼器

在傳送數據時可以在數據中 添加一個用於表示有用數據長度的欄位 ,在解碼時讀取出這個用於表明長度的欄位,同時讀取其他相關參數,即可知道最終需要的數據是什麼樣子的

LengthFieldBasedFrameDecoder 解碼器可以提供更為豐富的拆分方法,其構造方法有五個參數

public LengthFieldBasedFrameDecoder(
    int maxFrameLength,
    int lengthFieldOffset, int lengthFieldLength,
    int lengthAdjustment, int initialBytesToStrip)

參數解析

  • maxFrameLength 數據最大長度
    • 表示數據的最大長度(包括附加信息、長度標識等內容)
  • lengthFieldOffset 數據長度標識的起始偏移量
    • 用於指明數據第幾個位元組開始是用於標識有用位元組長度的,因為前面可能還有其他附加信息
  • lengthFieldLength 數據長度標識所占位元組數(用於指明有用數據的長度)
    • 數據中用於表示有用數據長度的標識所占的位元組數
  • lengthAdjustment 長度表示與有用數據的偏移量
    • 用於指明數據長度標識和有用數據之間的距離,因為兩者之間還可能有附加信息
  • initialBytesToStrip 數據讀取起點
    • 讀取起點,不讀取 0 ~ initialBytesToStrip 之間的數據

參數圖解

file

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 0 (= do not strip header)
  
BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+

從 0 開始即為長度標識,長度標識長度為 2 個位元組

0x000C 即為後面 HELLO, WORLD 的長度

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" |
+--------+----------------+      +----------------+

從 0 開始即為長度標識,長度標識長度為 2 個位元組,讀取時從第二個位元組開始讀取(此處即跳過長度標識)

因為跳過了用於表示長度的 2 個位元組,所以此處直接讀取 HELLO, WORLD

lengthFieldOffset   = 2 (= the length of Header 1)
lengthFieldLength   = 3
lengthAdjustment    = 0
initialBytesToStrip = 0
  
BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
| Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
|  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+      +----------+----------+----------------+

長度標識前面還有 2 個位元組的其他內容(0xCAFE),第三個位元組開始才是長度標識,長度表示長度為 3 個位元組 (0x00000C)

Header1 中有附加信息,讀取長度標識時需要跳過這些附加信息來獲取長度

lengthFieldOffset   = 0
lengthFieldLength   = 3
lengthAdjustment    = 2 (= the length of Header 1)
initialBytesToStrip = 0
  
BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
|  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
| 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
+----------+----------+----------------+      +----------+----------+----------------+

從 0 開始即為長度標識,長度標識長度為 3 個位元組,長度標識之後還有 2 個位元組的其他內容(0xCAFE)

長度標識 (0x00000C) 表示的是從其後 lengthAdjustment(2 個位元組)開始的數據的長度,即 HELLO, WORLD,不包括 0xCAFE

lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
lengthAdjustment    = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
  
BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
+------+--------+------+----------------+      +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+      +------+----------------+

長度標識前面有 1 個位元組的其他內容,後面也有 1 個位元組的其他內容,讀取時從長度標識之後 3 個位元組處開始讀取,即讀取 0xFE HELLO, WORLD

使用

通過 EmbeddedChannel 對 handler 進行測試

public class EncoderStudy {
    public static void main(String[] args) {
        // 模擬伺服器
        // 使用EmbeddedChannel測試handler
        EmbeddedChannel channel = new EmbeddedChannel(
                // 數據最大長度為1KB,長度標識前後各有1個位元組的附加信息,長度標識長度為4個位元組(int)
                new LengthFieldBasedFrameDecoder(1024, 1, 4, 1, 0),
                new LoggingHandler(LogLevel.DEBUG)
        );

        // 模擬客戶端,寫入數據
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        send(buffer, "Hello");
        channel.writeInbound(buffer);
        send(buffer, "World");
        channel.writeInbound(buffer);
    }

    private static void send(ByteBuf buf, String msg) {
        // 得到數據的長度
        int length = msg.length();
        byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
        // 將數據信息寫入buf
        // 寫入長度標識前的其他信息
        buf.writeByte(0xCA);
        // 寫入數據長度標識
        buf.writeInt(length);
        // 寫入長度標識後的其他信息
        buf.writeByte(0xFE);
        // 寫入具體的數據
        buf.writeBytes(bytes);
    }
}

運行結果

146  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ca 00 00 00 05 fe 48 65 6c 6c 6f                |......Hello     |
+--------+-------------------------------------------------+----------------+

146  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ca 00 00 00 05 fe 57 6f 72 6c 64                |......World     |
+--------+-------------------------------------------------+----------------+

本文由傳智教育博學谷教研團隊發佈。

如果本文對您有幫助,歡迎關註點贊;如果您有任何建議也可留言評論私信,您的支持是我堅持創作的動力。

轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2023-01-05 一、CommonResult工具 1、CommonResult工具的目的是:為了方便團隊開發。一般是在使用非同步的時候使用。 2、CommonResult工具的使用: (1)前端發送非同步請求到servlet。 (2)servlet給響應數據的時候,將所有數據都封裝到CommonR ...
  • 網關常見問題 侯門一入深似海,從此蕭郎是路人 1、什麼是網關 總而言之,網關就是統一入口、鑒權校驗、動態路由和過濾封裝。 2、為什麼需要網關 微服務架構下,單體應用被切割成多個微服務,如果將所有的微服務直接對外暴露,會出現安全方面的各種問題,且內外耦合嚴重。 Gateway 網關架構可以細到為每一個 ...
  • 使用java代碼操作rabbitmq時,首先需要一個有創建用戶等許可權的管理員賬號,需要在rabbitmq的後臺管理頁面手動創建這個賬號,系統推薦的這幾個tag可以讓賬號有rabbitmq後臺管理頁面的訪問許可權 圖一 管理賬號創建完成後就可以在代碼中操作新增編輯mq賬號及vhost等等了,點擊rabb ...
  • 聲明 本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯繫我立即刪除! 本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K ...
  • 小學妹說要畢業了,學了一學期Python等於沒學,現在要做畢設做不出來,讓我幫幫她,晚上去她家吃夜宵。 當時我心想,這不是分分鐘的事情,還要去她家,男孩子晚上不要隨便出門,要學會保護好自己,於是我花了十分鐘給她寫了一個發過去,這下不用去她家了~ 代碼實戰 主要代碼,完整代碼素材、包括其它版本學生管理 ...
  • JZ84 二叉樹中和為某一值的路徑(三) 題目 給定一個二叉樹root和一個整數值 sum ,求該樹有多少路徑的的節點值之和等於 sum 。 1.該題路徑定義不需要從根節點開始,也不需要在葉子節點結束,但是一定是從父親節點往下到孩子節點 2.總節點數目為n 3.保證最後返回的路徑個數在整形範圍內(即 ...
  • 引言 spring實現的bean自動註入在項目開發中是一個經常使用到的功能,但自動裝配兩個或多個bean時,會拋出NoUniqueBeanDefinitionException:No qualifying bean of type 'com' available: expected single m ...
  • RESTfulL是一種網路應用程式的設計風格和開發方式,即介面請求方式和路徑的一種風格。 普通風格: localhost:8080/add?a=1&b=2 RestFul風格: localhost:8080/add/1/2 GET 獲取: localhost:8080/item/1 POST 新增: ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...