粘包和半包的解决(三)

简介: 粘包和半包的解决(三)

调整客户端代码

1. public class HelloWorldClient {
2. static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
3. 
4. public static void main(String[] args) {
5. NioEventLoopGroup worker = new NioEventLoopGroup();
6. try {
7. Bootstrap bootstrap = new Bootstrap();
8.             bootstrap.channel(NioSocketChannel.class);
9.             bootstrap.group(worker);
10.             bootstrap.handler(new ChannelInitializer<SocketChannel>() {
11. @Override
12. protected void initChannel(SocketChannel ch) throws Exception {
13.                     log.debug("connetted...");
14.                     ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
15.                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
16. @Override
17. public void channelActive(ChannelHandlerContext ctx) throws Exception {
18.                             log.debug("sending...");
19. Random r = new Random();
20. char c = 'a';
21. ByteBuf buffer = ctx.alloc().buffer();
22. for (int i = 0; i < 10; i++) {
23. byte length = (byte) (r.nextInt(16) + 1);
24. // 先写入长度
25.                                 buffer.writeByte(length);
26. // 再
27. for (int j = 1; j <= length; j++) {
28.                                     buffer.writeByte((byte) c);
29.                                 }
30.                                 c++;
31.                             }
32.                             ctx.writeAndFlush(buffer);
33.                         }
34.                     });
35.                 }
36.             });
37. ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
38.             channelFuture.channel().closeFuture().sync();
39. 
40.         } catch (InterruptedException e) {
41.             log.error("client error", e);
42.         } finally {
43.             worker.shutdownGracefully();
44.         }
45.     }
46. }

客户端发送的数据

服务端接收的数据

1. 偏移量为 0 处的 2 字节长度字段,不剥离标头
2. 此示例中长度字段的值为 12 (0x0C), 表示“HELLO, WORLD”的长度。
3. 默认情况下,解码器假定长度字段表示长度字段后面的字节数。
4. 因此,可以使用简单的参数组合对其进行解码。
5.    lengthFieldOffset   = 0
6.    lengthFieldLength   = 2
7.    lengthAdjustment    = 0
8.    initialBytesToStrip = 0 (= do not strip header)
9. 
10.    BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
11.    +--------+----------------+      +--------+----------------+
12.    | Length | Actual Content |----->| Length | Actual Content |
13.    | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
14.    +--------+----------------+      +--------+----------------+
1. 偏移量为 0 处的 2 字节长度字段,条带标头
2. 因为我们可以通过调用 ByteBuf.readableBytes()来获取内容的长度,
3. 所以你可能希望通过指定 initialBytesToStrip来去除长度字段。
4. 在此示例中,我们指定了 2,与长度字段的长度相同,以去除前两个字节。
5.    lengthFieldOffset   = 0
6.    lengthFieldLength   = 2
7.    lengthAdjustment    = 0
8.    initialBytesToStrip = 2 (= the length of the Length field)
9. 
10.    BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
11.    +--------+----------------+      +----------------+
12.    | Length | Actual Content |----->| Actual Content |
13.    | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
14.    +--------+----------------+      +----------------+
1. 偏移量为 0 处的 2 字节长度字段,不要剥离标头,长度字段表示整个消息的长度
2. 在大多数情况下,长度字段仅表示消息正文的长度,如前面的示例所示。
3. 但是,在某些协议中,长度字段表示整个消息的长度,包括消息标头。在这种情况下,
4. 我们指定一个非零长度调整。由于此示例消息中的长度值始终大于正文长度 2,
5. 因此我们将 -2 指定为 lengthAdjust 以进行补偿。
6.    lengthFieldOffset   =  0
7.    lengthFieldLength   =  2
8.    lengthAdjustment    = -2 (= the length of the Length field)
9.    initialBytesToStrip =  0
10. 
11.    BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
12.    +--------+----------------+      +--------+----------------+
13.    | Length | Actual Content |----->| Length | Actual Content |
14.    | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
15.    +--------+----------------+      +--------+----------------+
1.  字节标头末尾的 3 字节长度字段,不要剥离标头
2. 以下消息是第一个示例的简单变体。消息前面附加了一个额外的标头值。 
3. lengthAdjust 再次为零,因为解码器在计算帧长度时始终考虑预置数据的长度。
4.    lengthFieldOffset   = 2 (= the length of Header 1)
5.    lengthFieldLength   = 3
6.    lengthAdjustment    = 0
7.    initialBytesToStrip = 0
8. 
9.    BEFORE DECODE (17 bytes)
10.    +----------+----------+----------------+     
11.    | Header 1 |  Length  | Actual Content |      
12.    |  0xCAFE  | 0x00000C | "HELLO, WORLD" |     
13.    +----------+----------+----------------+ 
14. 
15.    AFTER DECODE (17 bytes)
16.    +----------+----------+----------------+
17.    | Header 1 |  Length  | Actual Content |
18.    |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
19.    +----------+----------+----------------+
1. 字节标头开头的 3 字节长度字段,不要剥离标头
2. 这是一个高级示例,显示了长度字段和消息正文之间有一个额外标头的情况。
3. 您必须指定正 lengthAdjust, 以便解码器将额外的标头计入帧长度计算中。
4.    lengthFieldOffset   = 0
5.    lengthFieldLength   = 3
6.    lengthAdjustment    = 2 (= the length of Header 1)
7.    initialBytesToStrip = 0
8. 
9.    BEFORE DECODE (17 bytes)
10.    +----------+----------+----------------+      
11.    |  Length  | Header 1 | Actual Content |
12.    | 0x00000C |  0xCAFE  | "HELLO, WORLD" |     
13.    +----------+----------+----------------+     
14. 
15.    AFTER DECODE (17 bytes)
16.    +----------+----------+----------------+
17.    |  Length  | Header 1 | Actual Content |
18.    | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
19.    +----------+----------+----------------+
1. 字节长度字段位于 4 字节标头中间的偏移量 1,去除第一个标头字段和长度字段
2. 这是上述所有示例的组合。长度字段之前有前缀标头,长度字段之后有额外的标头。前面的标头会影响 lengthFieldOffset,而额外的标头会影响 lengthAdjust。我们还指定了一个非零的 initialBytesToStrip 来从帧中去除长度字段和前置标头。如果不想去除前面的标头,可以为 initialBytesToSkip 指定 0。
3.    lengthFieldOffset   = 1 (= the length of HDR1)
4.    lengthFieldLength   = 2
5.    lengthAdjustment    = 1 (= the length of HDR2)
6.    initialBytesToStrip = 3 (= the length of HDR1 + LEN)
7. 
8.    BEFORE DECODE (16 bytes)
9.    +------+--------+------+----------------+      
10.    | HDR1 | Length | HDR2 | Actual Content |
11.    | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      
12.    +------+--------+------+----------------+      
13. 
14.    AFTER DECODE (13 bytes)
15.    +------+----------------+
16.    | HDR2 | Actual Content |
17.    | 0xFE | "HELLO, WORLD" |
18.    +------+----------------+
1. 字节长度字段在偏移量1处4字节头的中间,
2. 去掉第一个头字段和长度字段,长度字段代表整个消息的长度
3. 让我们对前面的例子再做一个转折。与前面的示例的唯一区别是,
4. 长度字段表示整个消息的长度,而不是消息正文,就像第三个示例一样。
5. 我们必须将 HDR1 和长度的长度计算成 长度调整。
6. 请注意,我们不需要考虑 HDR2 的长度,因为长度字段已经包含整个标头长度。
7.    lengthFieldOffset   =  1
8.    lengthFieldLength   =  2
9.    lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)
10.    initialBytesToStrip =  3
11. 
12.    BEFORE DECODE (16 bytes)
13.    +------+--------+------+----------------+      
14.    | HDR1 | Length | HDR2 | Actual Content |
15.    | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      
16.


相关文章
|
6月前
|
缓存 移动开发 网络协议
tcp业务层粘包和半包理解及处理
tcp业务层粘包和半包理解及处理
106 1
|
6月前
|
JSON 移动开发 网络协议
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
158 0
|
网络协议 算法
|
6月前
|
XML 缓存 网络协议
面试题:TCP的粘包和拆包
面试题:TCP的粘包和拆包
56 1
|
6月前
|
存储 网络协议 算法
Netty使用篇:半包&粘包
Netty使用篇:半包&粘包
|
缓存 移动开发 网络协议
Netty 中的粘包和拆包详解
Netty 中的粘包和拆包详解
4313 1
|
移动开发
粘包和半包的解决(二)
粘包和半包的解决(二)
Netty(三)之数据之粘包拆包
Netty(三)之数据之粘包拆包
78 0
Netty(三)之数据之粘包拆包
|
存储 网络协议
TCP拆包和粘包的作用是什么
首先我们思考一个问题,应用层的传输一个10M的文件是一次性传输完成,而对于传输层的协议来说,为什么不是一次性传输完成呢。 这个有很多原因,比如稳定性,一次发送的数据越多,出错的概率越大。再比如说为了效率,网络中有时候存在并行的路径,拆分数据包就就能更好的利用这些并行的路径。再有,比如发送和接收数据的时候,都存在缓冲区,缓冲区是在内存中开辟的一块空间,目的是缓冲大量的应用频繁的通过网卡收发数据,这个时候,网卡只能一个一个处理应用的请求。当网卡忙不过来的时候,数据就需要排队了。也就是将数据放入缓冲区。如果每个应用都随意发送很大的数据,可能导致其他应用的实时性遭到破坏。
75 0
|
移动开发 网络协议 Java
TCP 粘包/拆包问题
《基础系列》
160 0
TCP 粘包/拆包问题