netty如何解决拆包、粘包?
一、何为拆包、粘包
1.TCP协议
拆包和粘包问题得从TCP协议说起了。
TCP协议是一个面向字节流得协议,它得性质是流式的,并没有分段。就像水流一样,你无法知道它什么时候开始,什么时候结束。
所以它会根据当前的套接字缓冲区的情况进行拆包和粘包。
下面展示一个TCP协议的传输过程
发送方 ——->向发送端缓冲区写入数据 —->TCP(缓冲区) ——> 发送TCP报文段 ——-> TCP(缓冲区) ——> 从接收缓冲端读取数据
发送端的字节流都会先写入缓冲区,然后再通过网络传到接收端缓冲区,最终由接收端读取数据。
当我们发送两个完整的报文到接收端时,正常情况下,接收端会收到两个完整的报文。
但是,也会出现下面情况:
- 接收端只接收到一个报文,它由发送的两个报文组成(粘包)
- 第一个报文中只有正常的一部分,第二个报文中有正常第一个报文剩余的部分和第二个报文的整个(拆包)
- 第一个报文中有正常的第一个报文的整个和第二个报文的部分,第二个报文只有剩余的部分(拆包)
对于上面的拆包和粘包我们只能将其放在应用层来处理了
常见的处理方式有:
- 在每次发送报文的末尾添加一个换行符或者其它特殊符号,这样在接收端通过这个换行符或者特殊符号来判断报文是否完整,如何不完整则缓存起来和下一个报文拼接
- 规定好报文的长度,不足的空位补齐,接收端按照指定长度截取数据
二、netty中处理拆包、粘包案例
定义netty服务端
public static void main(String[] args) {EventLoopGroup bossGroup=new NioEventLoopGroup();EventLoopGroup workGroup=new NioEventLoopGroup();try {ServerBootstrap serverBootstrap=new ServerBootstrap();serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)// 服务端自定义处理器.childHandler(new TestServerInitializer());ChannelFuture channelFuture = serverBootstrap.bind(8989).sync();channelFuture.channel().closeFuture().sync();}catch (Exception e){e.printStackTrace();}finally {bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}}
服务端自定义处理器
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();/*** 1) lengthFieldOffset //长度字段的偏差* 2) lengthFieldLength //长度字段占的字节数* 3) lengthAdjustment //添加到长度字段的补偿值* 4) initialBytesToStrip //从解码帧中第一次去除的字节数*/pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));//计算当前待发送消息的二进制字节长度,将该长度添加到ByteBuf的缓冲区头中pipeline.addLast(new LengthFieldPrepender(4));pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));pipeline.addLast(new TestServerHandler());}}
定义客户端
EventLoopGroup bossGroup=new NioEventLoopGroup();try {Bootstrap bootstrap=new Bootstrap();bootstrap.group(bossGroup).channel(NioSocketChannel.class)// 客户端自定义处理器.handler(new TestClientInitializer());ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8989).sync();channelFuture.channel().closeFuture().sync();}catch (Exception e){e.printStackTrace();}finally {bossGroup.shutdownGracefully();}
客户端自定义处理器
public class TestClientInitializer extends ChannelInitializer<SocketChannel>{@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();/*** 1) lengthFieldOffset //长度字段的偏差* 2) lengthFieldLength //长度字段占的字节数* 3) lengthAdjustment //添加到长度字段的补偿值* 4) initialBytesToStrip //从解码帧中第一次去除的字节数*/pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));//计算当前待发送消息的二进制字节长度,将该长度添加到ByteBuf的缓冲区头中pipeline.addLast(new LengthFieldPrepender(4));//将byte数据解码成Stringpipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));//将字符串编码成byte数据pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));pipeline.addLast(new TestClientHandler());}}
上面的例子是基于长度的解码器,可以按照指定长度截取
netty中常见的解码器有:
- DelimiterBasedFrameDecoder 基于分割符的解码器
- LengthFieldBasedFrameDecoder 基于长度的解码器
