tcp粘包拆包基本解决方案

duan2 duan2     2022-11-03     688

关键词:

上个小节我们浅析了在Netty的使用的时候TCP的粘包和拆包的现象,Netty对此问题提供了相对比较丰富的解决方案

 

Netty提供了几个常用的解码器,帮助我们解决这些问题,其实上述的粘包和拆包的问题,归根结底的解决方案就是发送端给远程端一个标记,告诉远程端,每个信息的结束标志是什么,这样,远程端获取到数据后,根据跟发送端约束的标志,将接收的信息分切或者合并成我们需要的信息,这样我们就可以获取到正确的信息了

 

例如,我们刚才的例子中,我们可以在发送的信息中,加一个结束标志,例如两个远程端规定以行来切分数据,那么发送端,就需要在每个信息体的末尾加上行结束的标志,部分代码如下:

修改BaseClientHandler的req的构造:

 

[java] view plain copy
 
  1. public BaseClientHandler()   
  2. //        req = ("BazingaLyncc is learner").getBytes();  
  3.         req = ("In this chapter you general, we recommend Java Concurrency in Practice by Brian Goetz. His book w"  
  4.                 + "ill give We’ve reached an exciting point—in the next chapter we’ll discuss bootstrapping, the process "  
  5.                 + "of configuring and connecting all of Netty’s components to bring your learned about threading models in ge"  
  6.                 + "neral and Netty’s threading model in particular, whose performance and consistency advantages we discuss"  
  7.                 + "ed in detail In this chapter you general, we recommend Java Concurrency in Practice by Brian Goetz. Hi"  
  8.                 + "s book will give We’ve reached an exciting point—in the next chapter we’ll discuss bootstrapping, the"  
  9.                 + " process of configuring and connecting all of Netty’s components to bring your learned about threading "  
  10.                 + "models in general and Netty’s threading model in particular, whose performance and consistency advantag"  
  11.                 + "es we discussed in detailIn this chapter you general, we recommend Java Concurrency in Practice by Bri"  
  12.                 + "an Goetz. His book will give We’ve reached an exciting point—in the next chapter;the counter is: 1 2222"  
  13.                 + "sdsa ddasd asdsadas dsadasdas" + System.getProperty("line.separator")).getBytes();  
  14.       

我们在我们巨长的req中末尾加了System.getProperty("line.separator"),这样相当于给req打了一个标记

 

 

打完标记,其实我们这个示例中的server中还不知道是以行为结尾的,所以我们需要修改server的handler链,在inbound链中加一个额外的处理链,判断一下,获取的信息按照行来切分,我们很庆幸,这样枯燥的代码Netty已经帮我们完美地完成了,Netty提供了一个LineBasedFrameDecoder这个类,顾名思义,这个类名字中有decoder,说明是一个解码器,我们再看看它的详细声明:

 

[java] view plain copy
 
  1. /** 
  2.  * A decoder that splits the received @link ByteBufs on line endings. 
  3.  * <p> 
  4.  * Both @code "\n" and @code "\r\n" are handled. 
  5.  * For a more general delimiter-based decoder, see @link DelimiterBasedFrameDecoder. 
  6.  */  
  7. public class LineBasedFrameDecoder extends ByteToMessageDecoder   
  8.   
  9.     /** Maximum length of a frame we‘re willing to decode.  */  
  10.     private final int maxLength;  
  11.     /** Whether or not to throw an exception as soon as we exceed maxLength. */  
  12.     private final boolean failFast;  
  13.     private final boolean stripDelimiter;  
  14.   
  15.     /** True if we‘re discarding input because we‘re already over maxLength.  */  
  16.     private boolean discarding;  
  17.     private int discardedBytes;  

它是继承ByteToMessageDecoder的,是将byte类型转化成Message的,所以我们应该将这个解码器放在inbound处理器链的第一个,所以我们修改一下Server端的启动代码:

 

 

[java] view plain copy
 
  1. package com.lyncc.netty.stickpackage.myself;  
  2.   
  3. import io.netty.bootstrap.ServerBootstrap;  
  4. import io.netty.channel.ChannelFuture;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelOption;  
  7. import io.netty.channel.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11. import io.netty.handler.codec.LineBasedFrameDecoder;  
  12. import io.netty.handler.codec.string.StringDecoder;  
  13.   
  14. import java.net.InetSocketAddress;  
  15.   
  16. public class BaseServer   
  17.   
  18.     private int port;  
  19.       
  20.     public BaseServer(int port)   
  21.         this.port = port;  
  22.       
  23.       
  24.     public void start()  
  25.         EventLoopGroup bossGroup = new NioEventLoopGroup(1);  
  26.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  27.         try   
  28.             ServerBootstrap sbs = new ServerBootstrap().group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))  
  29.                     .childHandler(new ChannelInitializer<SocketChannel>()   
  30.                           
  31.                         protected void initChannel(SocketChannel ch) throws Exception   
  32.                             ch.pipeline().addLast(new LineBasedFrameDecoder(2048));  
  33.                             ch.pipeline().addLast(new StringDecoder());  
  34.                             ch.pipeline().addLast(new BaseServerHandler());  
  35.                         ;  
  36.                           
  37.                     ).option(ChannelOption.SO_BACKLOG, 128)     
  38.                     .childOption(ChannelOption.SO_KEEPALIVE, true);  
  39.              // 绑定端口,开始接收进来的连接  
  40.              ChannelFuture future = sbs.bind(port).sync();    
  41.                
  42.              System.out.println("Server start listen at " + port );  
  43.              future.channel().closeFuture().sync();  
  44.          catch (Exception e)   
  45.             bossGroup.shutdownGracefully();  
  46.             workerGroup.shutdownGracefully();  
  47.           
  48.       
  49.       
  50.     public static void main(String[] args) throws Exception   
  51.         int port;  
  52.         if (args.length > 0)   
  53.             port = Integer.parseInt(args[0]);  
  54.          else   
  55.             port = 8080;  
  56.           
  57.         new BaseServer(port).start();  
  58.       
  59.   

这样,我们只是在initChannel方法中增加了一个LineBasedFrameDecoder这个类,其中2048是规定一行数据最大的字节数

 

我们再次运行,我们再看看效果:

技术分享图片

可以看到客户端发送的两次msg,被服务器端成功地两次接收了,我们要的效果达到了

 

我们将LineBasedFrameDecoder中的2048参数,缩小一半,变成1024,我们再看看效果:

技术分享图片

出现了异常,这个异常时TooLongFrameException,这个异常在Netty in Action中介绍过,帧的大小太大,在我们这个场景中,就是我们发送的一行信息大小是1076,大于了我们规定的1024所以报错了

 

 

我们再解决另一个粘包的问题,我们可以看到上节中介绍的那个粘包案例中,我们发送了100次的信息“BazingaLyncc is learner”,这个案例很特殊,这个信息是一个特长的数据,字节长度是23,所以我们可以使用Netty为我们提供的FixedLengthFrameDecoder这个解码器,看到这个名字就明白了大半,定长数据帧的解码器,所以我们修改一下代码:

BaseClientHandler:

 

[java] view plain copy
 
  1. package com.lyncc.netty.stickpackage.myself;  
  2.   
  3. import io.netty.buffer.ByteBuf;  
  4. import io.netty.buffer.Unpooled;  
  5. import io.netty.channel.ChannelHandlerContext;  
  6. import io.netty.channel.ChannelInboundHandlerAdapter;  
  7.   
  8. public class BaseClientHandler extends ChannelInboundHandlerAdapter  
  9.       
  10.     private byte[] req;  
  11.       
  12.     public BaseClientHandler()   
  13.         req = ("BazingaLyncc is learner").getBytes();  
  14. //        req = ("In this chapter you general, we recommend Java Concurrency in Practice by Brian Goetz. His book w"  
  15. //                + "ill give We’ve reached an exciting point—in the next chapter we’ll discuss bootstrapping, the process "  
  16. //                + "of configuring and connecting all of Netty’s components to bring your learned about threading models in ge"  
  17. //                + "neral and Netty’s threading model in particular, whose performance and consistency advantages we discuss"  
  18. //                + "ed in detail In this chapter you general, we recommend Java Concurrency in Practice by Brian Goetz. Hi"  
  19. //                + "s book will give We’ve reached an exciting point—in the next chapter we’ll discuss bootstrapping, the"  
  20. //                + " process of configuring and connecting all of Netty’s components to bring your learned about threading "  
  21. //                + "models in general and Netty’s threading model in particular, whose performance and consistency advantag"  
  22. //                + "es we discussed in detailIn this chapter you general, we recommend Java Concurrency in Practice by Bri"  
  23. //                + "an Goetz. His book will give We’ve reached an exciting point—in the next chapter;the counter is: 1 2222"  
  24. //                + "sdsa ddasd asdsadas dsadasdas" + System.getProperty("line.separator")).getBytes();  
  25.       
  26.       
  27.       
  28.     @Override  
  29.     public void channelActive(ChannelHandlerContext ctx) throws Exception   
  30.         ByteBuf message = null;  
  31.         for (int i = 0; i < 100; i++)   
  32.             message = Unpooled.buffer(req.length);  
  33.             message.writeBytes(req);  
  34.             ctx.writeAndFlush(message);  
  35.           
  36. //        message = Unpooled.buffer(req.length);  
  37. //        message.writeBytes(req);  
  38. //        ctx.writeAndFlush(message);  
  39. //        message = Unpooled.buffer(req.length);  
  40. //        message.writeBytes(req);  
  41. //        ctx.writeAndFlush(message);  
  42.       
  43.   
  44.     @Override  
  45.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)   
  46.         ctx.close();  
  47.       
  48.   
  49.   

BaseServer:

 

 

[java] view plain copy
 
  1. package com.lyncc.netty.stickpackage.myself;  
  2.   
  3. import io.netty.bootstrap.ServerBootstrap;  
  4. import io.netty.channel.ChannelFuture;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelOption;  
  7. import io.netty.channel.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11. import io.netty.handler.codec.FixedLengthFrameDecoder;  
  12. import io.netty.handler.codec.string.StringDecoder;  
  13.   
  14. import java.net.InetSocketAddress;  
  15.   
  16. public class BaseServer   
  17.   
  18.     private int port;  
  19.       
  20.     public BaseServer(int port)   
  21.         this.port = port;  
  22.       
  23.       
  24.     public void start()  
  25.         EventLoopGroup bossGroup = new NioEventLoopGroup(1);  
  26.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  27.         try   
  28.             ServerBootstrap sbs = new ServerBootstrap().group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))  
  29.                     .childHandler(new ChannelInitializer<SocketChannel>()   
  30.                           
  31.                         protected void initChannel(SocketChannel ch) throws Exception   
  32.                             ch.pipeline().addLast(new FixedLengthFrameDecoder(23));  
  33.                             ch.pipeline().addLast(new StringDecoder());  
  34.                             ch.pipeline().addLast(new BaseServerHandler());  
  35.                         ;  
  36.                           
  37.                     ).option(ChannelOption.SO_BACKLOG, 128)     
  38.                     .childOption(ChannelOption.SO_KEEPALIVE, true);  
  39.              // 绑定端口,开始接收进来的连接  
  40.              ChannelFuture future = sbs.bind(port).sync();    
  41.                
  42.              System.out.println("Server start listen at " + port );  
  43.              future.channel().closeFuture().sync();  
  44.          catch (Exception e)   
  45.             bossGroup.shutdownGracefully();  
  46.             workerGroup.shutdownGracefully();  
  47.           
  48.       
  49.       
  50.     public static void main(String[] args) throws Exception   
  51.         int port;  
  52.         if (args.length > 0)   
  53.             port = Integer.parseInt(args[0]);  
  54.          else   
  55.             port = 8080;  
  56.           
  57.         new BaseServer(port).start();  
  58.       
  59.   

我们就是在channelhandler链中,加入了FixedLengthFrameDecoder,且参数是23,告诉Netty,获取的帧数据有23个字节就切分一次

 

 

运行结果:

技术分享图片

技术分享图片

可以看见,我们获取到了我们想要的效果

 

 

当然Netty还提供了一些其他的解码器,有他们自己的使用场景,例如有按照某个固定字符切分的DelimiterBasedFrameDecoder的解码器

我们再次修改代码:

BaseClientHandler.java

 

[html] view plain copy
 
  1. package com.lyncc.netty.stickpackage.myself;  
  2.   
  3. import io.netty.buffer.ByteBuf;  
  4. import io.netty.buffer.Unpooled;  
  5. import io.netty.channel.ChannelHandlerContext;  
  6. import io.netty.channel.ChannelInboundHandlerAdapter;  
  7.   
  8. public class BaseClientHandler extends ChannelInboundHandlerAdapter  
  9.       
  10.     private byte[] req;  
  11.       
  12.     public BaseClientHandler()   
  13. //        req = ("BazingaLyncc is learner").getBytes();  
  14.         req = ("In this chapter you general, we recommend Java Concurrency in Practice by Brian Goetz. $$__ His book w"  
  15.                 + "ill give We’ve reached an exciting point—in the next chapter we’ll $$__ discuss bootstrapping, the process "  
  16.                 + "of configuring and connecting all of Netty’s components to bring $$__ your learned about threading models in ge"  
  17.                 + "neral and Netty’s threading model in particular, whose performance $$__ and consistency advantages we discuss"  
  18.                 + "ed in detail In this chapter you general, we recommend Java  $$__Concurrency in Practice by Brian Goetz. Hi"  
  19.                 + "s book will give We’ve reached an exciting point—in the next $$__ chapter we’ll discuss bootstrapping, the"  
  20.                 + " process of configuring and connecting all of Netty’s components $$__ to bring your learned about threading "  
  21.                 + "models in general and Netty’s threading model in particular, $$__ whose performance and consistency advantag"  
  22.                 + "es we discussed in detailIn this chapter you general, $$__ we recommend Java Concurrency in Practice by Bri"  
  23.                 + "an Goetz. His book will give We’ve reached an exciting $$__ point—in the next chapter;the counter is: 1 2222"  
  24.                 + "sdsa ddasd asdsadas dsadasdas" + System.getProperty("line.separator")).getBytes();  
  25.       
  26.       
  27.       
  28.     @Override  
  29.     public void channelActive(ChannelHandlerContext ctx) throws Exception   
  30.         ByteBuf message = null;  
  31. //        for (int i = 0; i 100; i++)   
  32. //            message = Unpooled.buffer(req.length);  
  33. //            message.writeBytes(req);  
  34. //            ctx.writeAndFlush(message);  
  35. //          
  36.         message = Unpooled.buffer(req.length);  
  37.         message.writeBytes(req);  
  38.         ctx.writeAndFlush(message);  
  39.         message = Unpooled.buffer(req.length);  
  40.         message.writeBytes(req);  
  41.         ctx.writeAndFlush(message);  
  42.       
  43.   
  44.     @Override  
  45.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)   
  46.         ctx.close();  
  47.       
  48.   
  49.   

我们在req的字符串中增加了“$$__”这样的切割符,然后再Server中照例增加一个DelimiterBasedFrameDecoder,来切割字符串:

 

 

[html] view plain copy
 
  1. ServerBootstrap sbs = new ServerBootstrap().group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))  
  2.                     .childHandler(new ChannelInitializer<SocketChannel>()   
  3.                           
  4.                         protected void initChannel(SocketChannel ch) throws Exception   
  5.                             ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,Unpooled.copiedBuffer("$$__".getBytes())));  
  6.                             ch.pipeline().addLast(new StringDecoder());  
  7.                             ch.pipeline().addLast(new BaseServerHandler());  
  8.                         ;  
  9.                           
  10.                     ).option(ChannelOption.SO_BACKLOG, 128)     
  11.                     .childOption(ChannelOption.SO_KEEPALIVE, true);  

我们在initChannel中第一个inbound中增加了DelimiterBasedFrameDecoder,且规定切割符就是“$$__”,这样就能正常切割了,我们看看运行效果:

 

技术分享图片

可以看到被分了20次读取,我们可以这样理解,客户端发送了2次req字节,每个req中有10个“$$__”,这样就是第11次切割的时候其实发送了粘包,第一个req中末尾部分和第二次的头部粘在了一起,作为第11部分的内容

 

而最后一部分的内容因为没有"$$__"切割,所以没有打印在控制台上~

 

其实这类的Handler还是相对比较简单的,真实的生产环境这些decoder只是作为比较基本的切分类,但是这些decoder还是很好用的~

 

希望讲的对您有所帮助~END~

12.netty中tcp粘包拆包问题及解决方法(代码片段)

...文介绍了tcp粘包拆包问题;3.本文po出了粘包拆包问题解决方案及源代码实现;【1】tcp粘包拆包问题refer2HowtodealwiththeproblemofpacketstickingandunpackingduringTCPtransmission?-编程知识【1.1】粘包拆包 查看详情

tcp的粘包拆包问题+解决方案

 为什么TCP有而UDP没有粘包❓1️⃣因为udp的数据包有保护边界。2️⃣tcp是以字节流的形式,也就是没有边界,所以应用层的数据在传输层的时候就可能会出现粘包和拆包问题。出现这种问题的原因图解 查看详情

tcp粘包拆包原因与解决方案

文章目录TCP粘包和拆包问题背景举例说明产生原因解决方案思考:UDP会不会产生粘包问题呢?TCP粘包和拆包问题背景TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不了解... 查看详情

tcp粘包拆包原因与解决方案

文章目录TCP粘包和拆包问题背景举例说明产生原因解决方案思考:UDP会不会产生粘包问题呢?TCP粘包和拆包问题背景TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不了解... 查看详情

tcp粘包拆包

粘包、拆包发生原因:发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行... 查看详情

tcp粘包拆包

一、什么是粘包拆包?粘包拆包是TCP协议传输中一种现象概念。TCP是传输层协议,他传输的是“流”式数据,TCP并不知道传输是哪种业务数据,或者说,并不关心。它只是根据缓冲区状况将数据进行包划分,然后进行传输... 查看详情

netty是如何解决tcp粘包拆包的?

点击关注公众号,Java干货及时送达作者:rickiyang出处:www.cnblogs.com/rickiyang/p/11074235.html我们都知道TCP是基于字节流的传输协议。那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么... 查看详情

粘包拆包(分包)半包

粘包、拆包、半包理解TCP是一种面向流的网络层传输协议,在使用TCP作为传输层协议时,可保证数据的顺序性和可靠性。应用层在使用TCP协议传输数据时,可采取两种方式:短链接:客户端同服务端完成一次通信(客户端只发送... 查看详情

面试官:什么是netty粘包拆包?怎么解决netty粘包拆包问题

哈喽!大家好,我是小奇,一位热爱分享的程序员小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧文章持续更新一、前言书接上回,昨天肯定是狗蛋通风报信,导致... 查看详情

封装一个带大小的封包,防止tcp粘包拆包(代码片段)

C++头文件#ifndefTCPWRAP_H#defineTCPWRAP_H#include<memory>#include<netinet/in.h>#include<string.h>#pragmapack(push,1)typedefstructsize_tlength;//包头(包体长度)char*body;//包体Packet;#pragmapac 查看详情

java网络编程——粘包拆包出现的原因及解决方式(代码片段)

在基于TCP协议的网络编程中,不可避免地都会遇到粘包和拆包的问题。什么是粘包和拆包?先来看个例子,还是上篇文章《Java网络编程——NIO的阻塞IO模式、非阻塞IO模式、IO多路复用模式的使用》中“IO多路复用模式... 查看详情

java网络编程——粘包拆包出现的原因及解决方式(代码片段)

在基于TCP协议的网络编程中,不可避免地都会遇到粘包和拆包的问题。什么是粘包和拆包?先来看个例子,还是上篇文章《Java网络编程——NIO的阻塞IO模式、非阻塞IO模式、IO多路复用模式的使用》中“IO多路复用模式... 查看详情

什么是粘包和拆包,netty如何解决粘包拆包?(代码片段)

Netty粘包拆包TCP粘包拆包是指发送方发送的若干包数据到接收方接收时粘成一包或某个数据包被拆开接收。如下图所示,client发送了两个数据包D1和D2,但是server端可能会收到如下几种情况的数据。上图中演示了粘包和拆... 查看详情

十二.netty入门到超神系列-tcp粘包拆包处理(代码片段)

...别完整的数据包了(TCP无消息保护边界),可能会出现粘包拆包的问题。粘包拆包理解下面我用一个图来带大家理解什么是粘包和拆包解释一下第一次传输没有问题,数据1和数据2没有粘合,也没有拆分第二次传输,... 查看详情

netty框架之编解码机制一(bytebuf以及tcp粘包拆包)(代码片段)

...解码之前,我们先说一下netty的ByteBuf,以及tcp的粘包和拆包。ne 查看详情

netty框架之编解码机制一(bytebuf以及tcp粘包拆包)(代码片段)

...解码之前,我们先说一下netty的ByteBuf,以及tcp的粘包和拆包。ne 查看详情

java网络编程——粘包拆包出现的原因及解决方式(代码片段)

在基于TCP协议的网络编程中,不可避免地都会遇到粘包和拆包的问题。什么是粘包和拆包?先来看个例子,还是上篇文章《Java网络编程——NIO的阻塞IO模式、非阻塞IO模式、IO多路复用模式的使用》中“IO多路复用模式... 查看详情

java网络编程——粘包拆包出现的原因及解决方式(代码片段)

在基于TCP协议的网络编程中,不可避免地都会遇到粘包和拆包的问题。什么是粘包和拆包?先来看个例子,还是上篇文章《Java网络编程——NIO的阻塞IO模式、非阻塞IO模式、IO多路复用模式的使用》中“IO多路复用模式... 查看详情