透过现象看本质,我找到了Netty粘包与半包的这几种解决方案

news2024/11/26 12:19:11

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 之间的数据

参数图解

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎样进行股票量化对冲策略分析?

股票量化对冲策略的分析需要从各方面去深入了解&#xff0c;就比如说明确量化和对冲的概念&#xff0c;可以先下载OA系统中“量化对冲 产品基础知识的学习&#xff0c;也要知道量化对冲产品在构建股票多头的同时&#xff0c;也构建期货空头。在市场不稳定的操作情绪之下&#x…

Git——初识git

1、git概述 1.1 简介 Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种 项目。 Git 易于学习&#xff0c;占地面积小&#xff0c;性能极快。 它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作 流分支等特性。其性能优于…

rtl8188eus Linux驱动移植

rtl8188eus Linux驱动移植 rlt8188eus作为无线USB网卡&#xff0c;可以给我们的Linux设备提供无线上网能力&#xff0c;也能配置为AP&#xff0c;给其它无线设备提供上网能力。在使用较低版本的内核时&#xff0c;内核中不含rtl8188eus驱动&#xff0c;因此尝试自己移植&#…

1、常见的存储设备

文章目录较为常见的存储设备机械硬盘简介固态硬盘简介U盘简介固态U盘MMC卡SD卡简介TF卡NM卡MS卡CF卡CFExpress卡磁带光盘较为常见的存储设备 目前较为常见的存储设备&#xff0c;从电脑用的&#xff1a; 机械硬盘固态硬盘U盘固态U盘 到单反相机、运动相机、手机、行车记录仪…

BigDecimal 基本使用和常用方法

背景 涉及到比较大的数字之间的计算&#xff0c;使用float、double这样的浮点数就不那么准确了。因为不论是float 还是double都是浮点数&#xff0c;而计算机是二进制的&#xff0c;浮点数会失去一定的精确度。所以在商业计算中基本要用java.math.BigDecimal 一、初始化使用…

蹭秦霄贤流量,郭德纲凌晨时分转发老秦动态

都知道德云社董事长王慧很忙&#xff0c;每天除了打理公司业务&#xff0c;还要照顾众多徒弟们的衣食住行。王慧作为德云社董事长&#xff0c;她的忙都在情理之中&#xff0c;而郭德纲作为德云社总班主&#xff0c;他的时间就更加弥足珍贵了。 可是谁能想到&#xff0c;就是这样…

VSCode配置C++开发环境:OpenCV

文章目录Linux编译调试配置OpenCVWin10编译调试配置OpenCV参考最近在做深度学习的C部署相关工作&#xff0c;于是写下这篇文档记录环境配置的过程。环境配置是一项非常繁琐的工作&#xff0c;无论从大学做相关作业还是到工作上。做这项工作需要技术的同时&#xff0c;还需要点运…

从0到1完成一个Vue后台管理项目(一、创建项目)

1.创建文件夹 这很简单&#xff0c;自己选一个盘&#xff0c;然后建一个文件夹即可 我选择的是D盘&#xff0c;最后的目录是vue-admin 注意&#xff1a;最后的目录一定要是英文 2.创建项目 初始化 npm init -y 局部安装vue-cli vue-cli的版本一直在更新&#xff0c;所以不推…

品牌舆情总结及品牌修复,品牌舆情监测监控怎么做?

如今互联网大数据时代&#xff0c;信息流通越来越快&#xff0c;影响覆盖面广。品牌一旦出现舆论&#xff0c;很容易引起大范围的热议&#xff0c;对品牌发展造成不利影响&#xff0c;进而影响品牌声誉。接下来TOOM舆情监测小编带您简单了解品牌舆情总结及品牌修复&#xff0c;…

Linux_Study

文章目录1.操作系统概述1.1 操作系统的作用1.2 常见的操作系统2.Linux介绍2.1 诞生2.2 Linux内核3. Linux基础操作3.1 虚拟机快照3.2 Linux 目录结构3.3 命令、命令行3.4 ls命令&#xff08;列表命令&#xff09;3.5 cd/pwd命令&#xff08;目录命令&#xff09;3.6 相对路径、…

OPENCPU学习---开发环境搭建

BC260Y-CN模组移远公司提供了QuecOpen的开发方案&#xff0c;可以通过提供的SDK中相应的接口快速进行应用开发。开发环境的搭建&#xff0c;首先要准备好开发板、SDK包以及下载工具&#xff0c;准备好后进行编译配置即可进行开发。目前移远提供的BC260Y开发SDK版本为&#xff1…

Lambda表达式超详细总结(简单易懂)

文章目录1、什么是Lambda表达式2、为什么使用Lambda表达式3、函数式接口&#xff08;lambda表达式的使用前提&#xff09;4、推导Lambda表达式5、Lambda表达式语法1、什么是Lambda表达式 Lambda表达式&#xff0c;也可称为闭包。其本质属于函数式编程的概念&#xff0c;是Java…

js判断元素是否在可视区域内

基本概念 首先需要搞清楚 clientHeight、scrollTop 的区别&#xff0c;通俗地说&#xff0c;clientHeight 指的是网页可视区域的高度&#xff0c;scrollTop 指的是网页被卷起来的高度&#xff0c;可以参考这篇文章&#xff1a;彻底搞懂clientHeight、offsetHeight、scrollHeig…

SQL -- MySQL 初识

SQL SQL的概述&#xff1a; SQL全称&#xff1a; Structured Query Language&#xff0c;是结构化查询语言&#xff0c;用于访问和处理数据库的标准的计算机语言。 SQL语言1974年由Boyce和Chamberlin提出&#xff0c;并首先在IBM公司研制的关系数据库系统System上实现美国国家…

入门远程连接技术

目录 1、ssh实验 要求&#xff1a;a.两台机器&#xff1a;第一台机器作为客户端&#xff0c;第二台机器作为服务器&#xff0c;在第一台使用rhce用户免密登录第二台机器。b.禁止root用户远程登录和设置三个用户sshuser1, sshuser2, sshuser3&#xff0c; 只允许sshuser3登录&…

DOM(二):节点操作、事件高级

节点操作节点层级事件高级事件对象节点概述 节点至少拥有nodeType&#xff08;节点类型&#xff09;、nodeName&#xff08;节点名称&#xff09;、nodeValue&#xff08;节点值&#xff09;这三个基本属性。 元素节点 nodeType为1属性节点 nodeType为2文本节点 nodeType为3 &…

字节前端必会vue面试题集锦

Vue3有了解过吗&#xff1f;能说说跟vue2的区别吗&#xff1f; 1. 哪些变化 从上图中&#xff0c;我们可以概览Vue3的新特性&#xff0c;如下&#xff1a; 速度更快体积减少更易维护更接近原生更易使用 1.1 速度更快 vue3相比vue2 重写了虚拟Dom实现编译模板的优化更高效的…

3、数据库中的约束数据库设计

约束 * 概念&#xff1a; 对表中的数据进行限定&#xff0c;保证数据的正确性、有效性和完整性。 * 分类&#xff1a; 1. 主键约束&#xff1a;primary key 2. 非空约束&#xff1a;not null 3. 唯一约束&#xff1a;unique 4. 外键约束&#xff1a;forei…

3.0里程碑:Topomel Box 现已在微软商店可用

猿友好&#xff01; 今天是个大喜的日子 我的程序 Topomel Box 的第三个版本(3.0)正式上架微软商店。通过微软商店这个统一的分发渠道&#xff0c;Topomel Box 有望与更多国家的用户见面并提供服务。 安装方法很简单&#xff0c;只需要打开微软商店&#xff08;Microsoft Sto…

⚡️【数据结构】二叉树的概念和相关知识总结

&#x1f332;目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树概念及结构 2.1概念 2.2满二叉树 2.3完全二叉树 2.4满二叉树和完全二叉树的区别 2.5二叉树的性质 2.6…