【Netty入门】TCP 粘包/拆包问题产生原因
来源:互联网 发布:淘宝买家达到钻号 编辑:程序博客网 时间:2024/06/08 10:18
TCP粘包/分包问题的由来
因为TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
这样说可能比较抽象,下面举例来说明TCP拆包/粘包问题!
- 图解:如果客户端分别发送两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,可能会出现四种情况。
(1)服务端分别读取到D1和D2,没有产生粘包和拆包的情况,如下图:
(2)服务端一次接收到二个数据包,D1和D2粘合在一起,被成为TCP粘包。如下图:
(3)服务端分二次读取到了二个数据包,第一次读取到了完整的D1包和D2包的一部分,第二次读取到了D2包的剩余部分,这被成为TCP拆包(D2拆包),如下图:
(4)服务器还是分二次读取到了二个数据包,但第一次是读取到了D1包的部分内容 ,第二次读取到了D1包剩余部分和完整的D2包,这被成为TCP拆包(D1拆包),如下图:
- 代码示例:
服务端代码
public class Server4 { public static void main(String[] args) throws SigarException { //boss线程监听端口,worker线程负责数据读写 EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); try{ //辅助启动类 ServerBootstrap bootstrap = new ServerBootstrap(); //设置线程池 bootstrap.group(boss,worker); //设置socket工厂 bootstrap.channel(NioServerSocketChannel.class); //设置管道工厂 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //获取管道 ChannelPipeline pipeline = socketChannel.pipeline(); //处理类 pipeline.addLast(new ServerHandler4()); } }); //绑定端口 ChannelFuture future = bootstrap.bind(8866).sync(); System.out.println("server start ...... "); //等待服务端监听端口关闭 future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { //优雅退出,释放线程池资源 boss.shutdownGracefully(); worker.shutdownGracefully(); } }}class ServerHandler4 extends SimpleChannelInboundHandler { //用于记录次数 private int count = 0; //读取客户端发送的数据 @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String c = new String(req,"UTF-8").substring(0, req.length - System.getProperty("line.separator").length()); count++; System.out.println("RESPONSE--------"+c+";"+" @ "+count); } //新客户端接入 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelActive"); } //客户端断开 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelInactive"); } //异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //关闭通道 ctx.channel().close(); //打印异常 cause.printStackTrace(); }}
客户端代码:
public class Client4 { public static void main(String[] args) { //worker负责读写数据 EventLoopGroup worker = new NioEventLoopGroup(); try { //辅助启动类 Bootstrap bootstrap = new Bootstrap(); //设置线程池 bootstrap.group(worker); //设置socket工厂 bootstrap.channel(NioSocketChannel.class); //设置管道 bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //获取管道 ChannelPipeline pipeline = socketChannel.pipeline(); //处理类 pipeline.addLast(new ClientHandler4()); } }); //发起异步连接操作 ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync(); //等待客户端链路关闭 futrue.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //优雅的退出,释放NIO线程组 worker.shutdownGracefully(); } }}class ClientHandler4 extends SimpleChannelInboundHandler<String> { //接受服务端发来的消息 @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("server response : "+msg); } //与服务器建立连接 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //给服务器发消息 ByteBuf message = null; byte[] req = " I am client ".getBytes(); //发送50次消息 for (int i = 0; i < 50; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.channel().writeAndFlush(message); } } //与服务器断开连接 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelInactive"); } //异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //关闭管道 ctx.channel().close(); //打印异常信息 cause.printStackTrace(); }}
服务端运行结果:
分析:通过代码可知,客户端向服务端发送了50条消息,正常结果是服务端应该接收了50条消息,但服务端的运行结果显示只收到了两条客户端的消息,由图知,第一条消息包含37 个I am client,而第二条消息包含13个I am client。这明显是出现了TCP粘包问题。
出现TCP粘包/分包的原因
1.应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象;
2.进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
3.以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。
图解:
TCP粘包/分包的解决方法
1.消息定长
例如:每个报文的大小固定为200个字节,如果不够,空位补空格
对应Netty中的定长类 :FixedLengthFrameDecoder
2.在包尾都增加特殊字符进行分割
例如:加回车、加换行、FTP协议等
对应Netty中的类
- 自定义分隔符类 :DelimiterBasedFrameDecoder
- 行分隔符类:LineBasedFrameDecoder
3.将消息分为消息头和消息体
例:在消息头中包含表示消息总长度的字段,然后进行业务逻辑的处理。
对应Netty中的基于消息头指定消息长度类:LengthFieldBasedFrameDecoder
4.更复杂的应用层协议
解决TCP粘包/分包问题的实例请阅读我的下一篇博文:解决TCP粘包/分包的实例
本人才疏学浅,若有错误,请指出
谢谢!
- 【Netty入门】TCP 粘包/拆包问题产生原因
- Netty初探-解决TCP粘包/拆包问题
- 聊一聊Netty TCP粘包/拆包问题的解决办法
- Netty实践(二):TCP拆包、粘包问题
- Netty -- TCP粘包/拆包
- Netty解决半包(TCP粘包/拆包导致)读写问题
- Netty解决半包(TCP粘包/拆包导致)读写问题
- Netty解决半包(TCP粘包/拆包导致)读写问题
- Netty解决TCP粘包/拆包导致的半包读写问题
- TCP粘包和拆包原因
- TCP粘包和拆包原因
- netty权威指南--------第四章TCP粘包/拆包问题
- netty权威指南--------第四章TCP粘包/拆包问题
- 【Netty4.x】Netty TCP粘包/拆包问题的解决办法(二)
- netty解决TCP网络传输中的拆包与粘包问题
- Netty权威指南之TCP粘包和拆包
- Netty学习之TCP粘包/拆包
- Netty中处理TCP粘包和拆包
- 2、如何给未知宽高的图片垂直居中?有几种办法?
- 剑指offer(一) 编程语言 sizeof typedef 赋值运算符函数 总结
- phpMyadmin 配置
- 求最长公共子序列
- hash算法 (hashmap 实现原理)
- 【Netty入门】TCP 粘包/拆包问题产生原因
- EasyML 快速入门
- 读书笔记之Builder模式
- canvas实现鼠标划线
- jQuery Mobile 滚屏事件
- IOS真机调试:swift工程在真机上运行崩溃的解决方案
- 如何用Hive搭建数据仓库
- 循环的嵌套
- Java中ArrayList类简单Demo