欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > Netty笔记9:粘包半包

Netty笔记9:粘包半包

2025/3/9 10:57:14 来源:https://blog.csdn.net/qq_28911061/article/details/146012977  浏览:    关键词:Netty笔记9:粘包半包

Netty笔记1:线程模型

Netty笔记2:零拷贝

Netty笔记3:NIO编程

Netty笔记4:Epoll

Netty笔记5:Netty开发实例

Netty笔记6:Netty组件

Netty笔记7:ChannelPromise通知处理

Netty笔记8:ByteBuf使用介绍

Netty笔记9:粘包半包

Netty笔记10:LengthFieldBasedFrameDecoder

Netty笔记11:编解码器

Netty笔记12:模拟Web服务器

Netty笔记13:序列化

文章目录

  • 前言
  • 什么是粘包/半包
  • 那如何解决粘包,半包?

前言

粘包/半包是一个很重要的概念,在网络数据传输中一定存在的问题,我们需要理解和会解决,以及运用Netty提供的工具解决。

什么是粘包/半包

粘包:在客户端和服务端之间,会维持一个连接,可以发送多个数据包,但是如果发送的网络数据包太小,那么它会启用Nagle算法,对较小的数据包进行合并,待超时或者数据包大小够大时再发送,也是因为这个原因,TCP的网络延迟会比UDP高一些,服务端对于这样合并发送的消息就无法区分哪些数据是分开发送的,也就是粘包;服务器再接收到数据后,放到缓冲区中,如果消息没有被及时从缓冲区取走,下次再取数据的时候可能会一次取出多个数据包的情况,产生粘包现象;

之前提到UDP,它本身作为无连接的不可靠传输协议,适合频繁发送数据较小的数据包,它不会对数据包进行合并发送,也就是发送什么,就接收到什么。

拆包:服务器将粘粘在一起的数据包进行拆分;

半包/分包:网络传输的套接字的发送缓冲区不够,或者达到了TCP最大报文长度(MSS)大小,导致接收到的数据不完整,还需要接收剩余的数据;

下面模拟粘包场景

修改clienthandler,尝试在客户端建立连接时发送100个数据包给服务端

    @Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 连接成功后激活这个方法for (int i = 0; i < 100; i++) {// 这里消息,再次用分隔符进行分割保证100个数据都是独立的数据包ctx.writeAndFlush(Unpooled.copiedBuffer(("我已经连上了"+System.getProperty("line.separator")).getBytes(CharsetUtil.UTF_8)));}}

之后在服务端接收数据,并统计接收次数

public class ServerHandler extends ChannelInboundHandlerAdapter {static int count = 0;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buffer = (ByteBuf)msg;System.out.println("接收到数据:" + buffer.toString(CharsetUtil.UTF_8));System.out.println("记录次数:" + ++count);System.out.println("ok--------------------------------");super.channelRead(ctx, msg);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);}
}

结果如下,服务端对100个的接收,其实只是读取了一次,甚至于我们增加了分隔符,也还是把这100个数据包,合并成一个发送了过来

image-20240613215519695

TCP最大数据长度就等于65535-20(20是固定的IP头部)=65495,一个http能发送的最大报文长度受诸多影响,如MSS,网络环境,应用层协议等,所以,理论上是小于65495这个值的。

会出现粘包,半包,是 由TCP传输层协议决定的,因为TCP不知道传输的数据多少字节是一个包,所以,这个现象是由应用层解决的。

那如何解决粘包,半包?

方法一:TCP不知道多少字节算一个包,所以我们可以添加分隔符,而这个分隔符是需要双端都要统一的,不然一端以回车换行为分隔符,另一端以空格为分隔符,就会造成数据分包不对;

方法二:指定数据长度,需要双端统一,需要在传输的字节中,添加数据包长度,在另一端读取解码时就可以知道读取多少为一个数据包;

方法一示例:

那我们定义一个回车换行符为分隔符,并添加到channelInitializer中;

注意:我这里以System.getProperty("line.separator")为分隔符,这个变量取自计算机操作系统,win和linux他们的值不同,所以,需要明确这个值,我这里是偷懒了。

如果说是回车换行为分隔符的解码器,Netty已经提供了一个LineBasedFrameDecoder

 ByteBuf byteBuf = Unpooled.wrappedBuffer(System.getProperty("line.separator").getBytes(StandardCharsets.UTF_8));socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));

image-20240613222114141

来看结果:

image-20240613222137004

方法二示例:

指定数据长度在数据包中,并解码,这个在Netty中是有实现的:

LengthFieldPrepender:经过这个过滤器会在发送的消息前增加一个长度字段,一般使用只有一个参数的构造器,指定长度的大小,这个长度不会改变整个消息的长度;如果要将长度累计到消息长度上,使用new LengthFieldPrepender(4,true)

一般是配合LengthFieldBasedFrameDecoder使用。

例如:

bootstrap.group(group, work)// server指定为ServerChannel.channel(NioServerSocketChannel.class)// 绑定端口.localAddress(8080)// 事件处理器.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline()// 数据写出时,增加一个4字节长度的字段.addLast(new LengthFieldPrepender(4)).addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4));

关于LengthFieldBasedFrameDecoder的详细说明,在下一篇。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词