调整客户端代码
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.