粘包和半包的解决(二)

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

短链接

发完马上关闭,下一次发送再次重新连接

1. public class HelloWorldClient {
2. static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
3. 
4. public static void main(String[] args) {
5. // 分 10 次发送
6. for (int i = 0; i < 10; i++) {
7.             send();
8.         }
9.     }
10. 
11. private static void send() {
12. NioEventLoopGroup worker = new NioEventLoopGroup();
13. try {
14. Bootstrap bootstrap = new Bootstrap();
15.             bootstrap.channel(NioSocketChannel.class);
16.             bootstrap.group(worker);
17.             bootstrap.handler(new ChannelInitializer<SocketChannel>() {
18. @Override
19. protected void initChannel(SocketChannel ch) throws Exception {
20.                     log.debug("conneted...");
21.                     ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
22.                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
23. @Override
24. public void channelActive(ChannelHandlerContext ctx) throws Exception {
25.                             log.debug("sending...");
26. ByteBuf buffer = ctx.alloc().buffer();
27.                             buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
28.                             ctx.writeAndFlush(buffer);
29. // 发完即关
30.                             ctx.close();
31.                         }
32.                     });
33.                 }
34.             });
35. ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
36.             channelFuture.channel().closeFuture().sync();
37.         } catch (InterruptedException e) {
38.             log.error("client error", e);
39.         } finally {
40.             worker.shutdownGracefully();
41.         }
42.     }
43. }

但是对于半包这种是不好解决掉的,因为接收方的缓冲区大小它是有限的

固定长度

让所有数据包长度固定(假设长度为 8 字节),服务器端加入

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

 

FixedLengthFrameDecoder

一种解码器,用于按固定的字节数拆分接收到的 ByteBufs。例如,如果您收到以下四个分段数据包:

  +---+----+------+----+

  | A | BC | DEFG | HI |

  +---+----+------+----+

 

A FixedLengthFrameDecoder(3) 会将它们解码为以下三个具有固定长度的数据包:

  +-----+-----+-----+

  | ABC | DEF | GHI |

  +-----+-----+-----+

修改客户端,客户端代码如下

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("conneted...");
14.                     ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
15. 
16.                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
17. @Override
18. public void channelActive(ChannelHandlerContext ctx) throws Exception {
19.                             log.debug("sending...");
20. Random r = new Random();
21. char c = 'a';
22. ByteBuf buffer = ctx.alloc().buffer();
23. for (int i = 0; i < 10; i++) {
24. byte[] bytes=new byte[8];
25. for (int j = 0; j < r.nextInt(8); j++) {
26.                                     bytes[j]=(byte)c;
27.                                 }
28.                                 c++;
29.                                 buffer.writeBytes(bytes);
30.                             }
31.                             ctx.writeAndFlush(buffer);
32.                         }
33.                     });
34.                 }
35.             });
36. ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
37.             channelFuture.channel().closeFuture().sync();
38.         } catch (InterruptedException e) {
39.             log.error("client error", e);
40.         } finally {
41.             worker.shutdownGracefully();
42.         }
43.     }
44. 
45. }

这里可以看到客户端是一口气发送完的,但在服务端的解析如下:

缺点是,数据包的大小不好把握

  • 长度定的太大,浪费
  • 长度定的太小,对某些数据包又显得不够

固定分隔符

服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

LineBasedFrameDecoder

一个解码器,用于在行尾拆分收到的 ByteBuf。

两者和"\n""\r\n"处理。

字节流应采用 UTF-8 字符编码或 ASCII。当前的实现使用直接byte强制char转换,然后将其与一些低范围的 ASCII 字符(如 '\n' or '\r')进行比较char。UTF-8 未对多字节代码点表示形式使用低范围 [0..0x7F] 字节值,因此此实现完全支持。

客户端在每条消息之后,加入 \n 分隔符

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. for (int j = 1; j <= r.nextInt(16)+1; j++) {
24.                                     buffer.writeByte((byte) c);
25.                                 }
26.                                 buffer.writeByte(10);//换行
27.                                 c++;
28.                             }
29.                             ctx.writeAndFlush(buffer);
30.                         }
31.                     });
32.                 }
33.             });
34. ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
35.             channelFuture.channel().closeFuture().sync();
36. 
37.         } catch (InterruptedException e) {
38.             log.error("client error", e);
39.         } finally {
40.             worker.shutdownGracefully();
41.         }
42.     }
43. }

客户端发送的数据

服务器端解析的数据

缺点,处理字符数据比较合适,但如果内容本身包含了分隔符(字节数据常常会有此情况),那么就会解析错误

预设长度

在发送消息前,先约定用定长字节表示接下来数据的长度

1. // 最大长度,长度偏移,长度占用字节,长度调整,剥离字节数
2. ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1, 0, 1));

LengthFieldBasedFrameDecoder

一种解码器,它按消息中长度字段的值动态拆分收到的 ByteBufs。当您解码二进制消息时,它特别有用,该二进制消息具有表示消息正文或整个消息长度的整数标头字段。

经典构造办法:

1. public LengthFieldBasedFrameDecoder(
2. int maxFrameLength,
3. int lengthFieldOffset, int lengthFieldLength,
4. int lengthAdjustment, int initialBytesToStrip) {
5. this(
6.                 maxFrameLength,
7.                 lengthFieldOffset, lengthFieldLength, lengthAdjustment,
8.                 initialBytesToStrip, true);
9.     }

创建新实例。

参数:

maxFrameLength:最大帧长度 ― 帧的最大长度。如果帧的长度大于此值, TooLongFrameException 将被抛出。

lengthFieldOffset:长度字段偏移量 – 长度字段的偏移量

lengthFieldLength:长度字段长度 – 长度字段的长度

lengthAdjustment:长度调整 – 要添加到长度字段值的补偿值

initialBytesToStrip :剥离字节数 ― 从解码帧中剥离的第一个字节数


相关文章
|
5月前
|
缓存 移动开发 网络协议
tcp业务层粘包和半包理解及处理
tcp业务层粘包和半包理解及处理
89 1
|
5月前
|
编解码 缓存 移动开发
TCP粘包/拆包与Netty解决方案
TCP粘包/拆包与Netty解决方案
88 0
|
4月前
|
Java
Netty传输object并解决粘包拆包问题
Netty传输object并解决粘包拆包问题
38 0
|
5月前
|
JSON 移动开发 网络协议
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
115 0
|
网络协议 算法
|
5月前
|
XML 缓存 网络协议
面试题:TCP的粘包和拆包
面试题:TCP的粘包和拆包
43 1
|
5月前
|
存储 网络协议 算法
Netty使用篇:半包&粘包
Netty使用篇:半包&粘包
|
缓存 移动开发 网络协议
Netty 中的粘包和拆包详解
Netty 中的粘包和拆包详解
4276 1
Netty(三)之数据之粘包拆包
Netty(三)之数据之粘包拆包
76 0
Netty(三)之数据之粘包拆包