Netty学习(二)—拆包粘包问题
来源:互联网 发布:java考试认证 编辑:程序博客网 时间:2024/06/05 08:40
Netty学习(二)—拆包粘包问题
无论是服务端还是客户端在进行数据发送收取的时候需要考虑TCP底层的粘包/拆包机制,因为如果不进行处理会造成收取的数据和预想的不一致;
个人主页:tuzhenyu’s page
原文地址:Netty学习(二)—拆包粘包问题
(0) 拆包粘包的原因
TCP粘包发生的原因
粘包现象出现的根本原因是TCP协议是一个面向数据流的通信协议,数据流中没有分界线;在发送或接收时并不会考虑业务数据的具体含义,而是会根据发送缓冲区或者接收缓冲区的情况进行数据包的划分;
为了提高发送效率,发送端会采用Nagle算法优化发送,将时间间隔较短的多次小数据包发送合并成一个大数据包发送,接收端不能辨别出合并情况就出现了粘包情况;
接收端的应用程序未能及时的读取接收缓冲区的数据,多次发送的数据堆积在套接字缓冲区,当应用程序读取时无法分辨就出现了粘包现象;
TCP拆包发生的原因
拆包现象出现的根本原因在于数据包的发送受到链路MTU的限制,也就是链路能传输的最大数据包的大小的限制;
当发送的数据包大小大于MTU-20(TCP报文头部)-20(IP报文头部)时就会出现TCP分组,将大数据包拆分成多个小数据包分别发送;
(1) 拆包粘包的解决策略
数据定长,发送数据报文的长度小于MSS(最大可发送长度)如果不够则用空格补齐,接收时也进行定长接收,能解决粘包问题,不能解决拆包问题;
特殊分割符,在数据结尾用回车换行符等分割,接收到的数据也以特殊字符进行分割,能解决粘包问题,不能解决拆包问题;
将数据长度和数据一起发送,将数据分为消息头和消息体两部分,消息头中包含数据总长度,能解决拆包和粘包问题;
(2) 粘包的示例
- 客户连续端发送数据到服务端
public void channelActive(ChannelHandlerContext ctx) throws Exception { byte[] req = null; ByteBuf buffer = null; for (int i=0;i<100;i++){ req = ("this is No."+i+" server sent the message ").getBytes(); buffer = Unpooled.buffer(req.length); buffer.writeBytes(req); ctx.writeAndFlush(buffer); }}
- 服务端接收数据
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); System.out.println("receive the bytes: "+new String(bytes,"UTF-8"));}
- 如果没有出现粘包现象则服务端会接收100次数据,输出100行数据;但是真实的情况服务端只接收了四次数据,出现了粘包现象,具体的是客户端发送粘包还是服务端接粘包不能判断;
receive the bytes: this is No.0 server sent the message this is No.1 server sent the message this is ...receive the bytes: No.27 server sent the message this is No.28 server sent the message this is...receive the bytes: s No.54 server sent the message this is No.55 server sent the message this is...receive the bytes: is No.81 server sent the message this is No.82 server sent the message this is...
(3) 分包的示例
- 客户端发送大数据包到服务端
public void channelActive(ChannelHandlerContext ctx) throws Exception { byte[] req = null; ByteBuf buffer = null; StringBuilder sb = new StringBuilder(); for (int i=0;i<100;i++){ sb.append("abcdefghijklmnopqrstuvwxyz"); } req = sb.toString().getBytes(); buffer = Unpooled.buffer(req.length); buffer.writeBytes(req); ctx.writeAndFlush(buffer);}
- 服务端接收数据,每接一次输出一行
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); System.out.println("receive the bytes: "+new String(bytes,"UTF-8"));}
- 按照预想的效果如果没有分包则客户端发送一次,服务端接收输出一次;实际上由于发送的数据包过大则客户端分多次进行发送,服务端也接收输出了多次;
receive the bytes: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz...receive the bytes: klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh...receive the bytes: uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst...
(4) LineBasedFrameDecoder解决粘包问题
LineBasedFrameDecoder解决粘包问题主要是通过对输入流以换行符“\n”对数据流进行分割,每次读取时只读取换行符之前的数据;LineBasedFrameDecoder不能解决拆包问题;
服务端发送数据,在每次发送结尾处添加换行符
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workGroup).channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new ServerHandler()); } });
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); String body = new String(bytes,"UTF-8"); System.out.println("the client says: "+ body); byte[] req = null; ByteBuf buffer = null; for (int i=0;i<100;i++){ req = ("this is No."+i+" server sent the message\n").getBytes(); buffer = Unpooled.buffer(req.length); buffer.writeBytes(req); ctx.writeAndFlush(buffer); } }
- 客户端接收数据,添加LineBasedFrameDecoder对数据流进行分割
Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024)); socketChannel.pipeline().addLast(new ClientHandler()); } });
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); String body = new String(bytes,"UTF-8"); System.out.println("the server says: "+ body); }
- 接收端添加LineBasedFrameDecoder解码器后对输入数据流进行分割,解决了粘包的情况
the server says: this is No.0 server sent the messagethe server says: this is No.1 server sent the messagethe server says: this is No.2 server sent the messagethe server says: this is No.3 server sent the messagethe server says: this is No.4 server sent the messagethe server says: this is No.5 server sent the messagethe server says: this is No.6 server sent the messagethe server says: this is No.7 server sent the messagethe server says: this is No.8 server sent the messagethe server says: this is No.9 server sent the messagethe server says: this is No.10 server sent the message
(4) DelimiterBasedFrameDecoder解决粘包问题
DelimiterBasedFrameDecoder特殊字符解码器和LineBasedFrameDecoder换行符解码器类似,都是在读取输入流时以特殊字符为分割符读取数据;DelimiterBasedFrameDecoder特殊字符解码器不能解决拆包问题,只能解决粘包问题;
服务端发送数据到客户端
byte[] req = null;ByteBuf buffer = null;for (int i=0;i<100;i++){ req = ("this is No."+i+" server sent the message$").getBytes(); buffer = Unpooled.buffer(req.length); buffer.writeBytes(req); ctx.writeAndFlush(buffer);}
- 客户端添加DelimiterBasedFrameDecoder解码器按照特殊字符对输入数据流进行分割;
Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes()))); socketChannel.pipeline().addLast(new ClientHandler()); } });
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); String body = new String(bytes,"UTF-8"); System.out.println("the server says: "+ body); }
(5) FixedLengthFrameDecoder定长解码器解决粘包问题
FixedLengthFrameDecoder定长解码器是提前设定消息数据的长度,接收端按照设定的长度进行数据流的分割;FixedLengthFrameDecoder只能解决粘包问题不能解决拆包问题;
客户端接收数据,添加定长解码器分割数据流;
Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(("hello world").getBytes().length)); socketChannel.pipeline().addLast(new ClientHandler()); } });
- 服务端发送特定长度的数据,如果数据长度不够则使用空格填充保证数据包长度的一定;
for (int i=0;i<100;i++){ req = ("hello world").getBytes(); buffer = Unpooled.buffer(req.length); buffer.writeBytes(req); ctx.writeAndFlush(buffer);}
总结
- 本文主要总结了粘包/拆包问题出现的原因和解决办法,以及Netty利用编码解码器对拆包/粘包问题的解决办法,并以LineBasedFrameDecoder,DelimiterBasedFrameDecoder和FixedLengthBasedFrameDecoder解码器为例解决TCP粘包问题;
- Netty学习(二)—拆包粘包问题
- Netty学习(二)-Helloworld Netty
- Netty与Marshalling结合发送对象—Netty学习二
- Java Netty 学习笔记(二)使用Netty编程
- netty学习(二)——Hello world!
- Netty学习系列(二)-- NIO介绍
- netty学习(二)基本组件
- 《Netty学习》(二)Hello World
- netty源码学习二(EventLoopGroup、EventLoop)
- Netty学习之二
- netty 学习记录二
- 学习 java netty (二) -- ServerBootstrap
- Netty学习总结(5)——Netty之TCP粘包/拆包问题的解决之道
- Netty从零开始(二)
- Netty学习总结(1)——Netty入门介绍
- Netty学习总结(6)——Netty使用注意事项
- Netty实践(二):TCP拆包、粘包问题
- Netty学习之旅(二)(HelloWorld)
- C#-动态编程
- .NET Entity Framework(EF)使用SqlQuery直接操作SQL查询语句或者执行过程
- C#获取本机MAC地址和IP
- (基于UDP协议/tcp协议)socket客户端,服务端
- leetcode: 76. Minimum Window Substring
- Netty学习(二)—拆包粘包问题
- 图说2017年C++大会
- (转)No 'Access-Control-Allow-Origin' header is present on the requested resource.'Ajax跨域访问解决方案
- LSV云端上传下载矢量数据
- Ubuntu终端常用的快捷键
- leetcode: 77. Combinations
- kNN(K-Nearest Neighbor)算法简介
- tail命令及时查看文件变化
- Java输入输出流