首页 > 其他分享 >netty之TCP粘包拆包问题解决

netty之TCP粘包拆包问题解决

时间:2023-04-23 10:34:57浏览次数:36  
标签:netty lengthFieldOffset TCP 粘包 发送 拆包 长度


TCP粘包拆包问题解决

什么TCP粘包和拆包问题

假设客户端向服务端连续发送了两个数据包,分别用ABC和DEF来表示,那么服务端收到的数据可以分为以下三种情况:

第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象。

netty之TCP粘包拆包问题解决_netty

第二种情况,接收端只收到一个数据包,这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。

netty之TCP粘包拆包问题解决_拆包_02

第三种情况,接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。

netty之TCP粘包拆包问题解决_粘包_03

为什么会发生TCP粘包和拆包

发生TCP粘包的原因:

  • 发送方写入的数据小于套接字缓冲区大小
  • 接收方读取套接字缓冲区数据不及时

发生TCP拆包的原因:

  • 发送方写入的数据大于套接字缓冲器大小
  • 发送方写入的数据大于MTU(Maximum Transmission Unit,最大传输单元),必须拆包

一个TCP报文最大能传输65536个字节,也就是16Kb。

根本原因:TCP是流式协议,数据无边界。

怎么解决

解决问题的根本手段就是找出消息的边界。

netty提供了以下三种方式解决TCP粘包和拆包问题:

  • DelimiterBasedFrameDecoder:分隔符。
  • LineBasedFrameDecoder:结束符\n。
  • FixedLengthFrameDecoder:固定长度。
  • LengthFieldBasedFrameDecoder+LengthFieldPrepender:自定义消息长度

DelimiterBasedFrameDecoder分隔符

DelimiterBasedFrameDecoder是通过发送方每条报文结束都添加特殊符号(例如netty之TCP粘包拆包问题解决_jvm_04_)对报文进行切割。

发送方需要自行编码,添加分隔符,编码如下:

ctx.writeAndFlush(Unpooled.copiedBuffer(("hello" + i + "$_").getBytes())); // 以$_结尾

接收方的解码如下:

ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1 << 10, Unpooled.copiedBuffer("$_".getBytes())));

缺点:发送的内容本身可能会出现分隔符,需要对发送的内容进行扫描并转义,接收到的内容也要进行反转义。

DelimiterBasedFrameDecoder提供了多个构造方法,最终调用的都是以下构造方法:

public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters)

参数说明:

  • maxLength:表示一行最大的长度,如果超过这个长度依然没有检测到分隔符,将会抛出TooLongFrameException。
  • failFast:与maxLength联合使用,表示超过maxLength后,抛出TooLongFrameException的时机。如果为true,则超出maxLength后立即抛出TooLongFrameException,不继续进行解码;如果为false,则等到完整的消息被解码后,再抛出TooLongFrameException异常。
  • stripDelimiter:解码后的消息是否去除分隔符。
  • delimiters:分隔符。我们需要先将分割符,写入到ByteBuf中,然后当做参数传入。

LineBasedFrameDecoder结束符\n

LineBasedFrameDecoder可以当成是一种特殊的DelimiterBasedFrameDecoder,其分隔符为\n或者\r\n。

发送方的编码如下:

ctx.writeAndFlush(Unpooled.copiedBuffer(("hello" + i + "\n").getBytes())); // 以\n结尾

接收方的解码如下:

ch.pipeline().addLast(new LineBasedFrameDecoder(1 << 10));

FixedLengthFrameDecoder固定长度

FixedLengthFrameDecoder是通过发送方固定每条报文长度均为n个字节,接收方也通过n个字节长度切分报文。

发送方需要自行补齐长度,编码如下:

ctx.writeAndFlush(Unpooled.copiedBuffer(("hello" + i + "          ").getBytes())); // 补齐长度为16

接收方的解码如下:

ch.pipeline().addLast(new FixedLengthFrameDecoder(16));

缺点:如果发送的内容比较小,需要补齐长度,空间浪费,如果要发送的内容突然变大,需要调整发送方和接收方的长度。

LengthFieldBasedFrameDecoder+LengthFieldPrepender自定义消息长度

LengthFieldPrepender对自定义消息长度进行编码。

LengthFieldBasedFrameDecoder对自定义消息长度进行解码。

LengthFieldBasedFrameDecoder的构造方法如下:

public LengthFieldBasedFrameDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {

参数说明:

  • byteOrder:数据存储采用大端模式或小端模式
  • maxFrameLength:发送的数据帧最大长度
  • lengthFieldOffset: 发送的字节数组中从下标lengthFieldOffset开始存放的是报文数据的长度。
  • lengthFieldLength: 在发送的字节数组中,报文数据的长度占几位,也就是字节数组bytes[lengthFieldOffset, lengthFieldOffset+lengthFieldLength]存放的是报文数据的长度
  • lengthAdjustment: 长度域的偏移量矫正。如果长度域的值,除了包含有效数据域的长度外,还包含了其他域(如长度域自身)长度,那么,就需要进行矫正。矫正的值为:包长-长度域的值-长度域偏移 – 长度域长。
  • initialBytesToStrip:接收到的发送数据包,去除前initialBytesToStrip位
  • failFast:为true表示读取到长度域超过maxFrameLength,就抛出一个TooLongFrameException。为false表示只有真正读取完长度域的值表示的字节之后,才会抛出TooLongFrameException,默认情况下设置为true,建议不要修改,否则可能会造成内存溢出。

场景一

netty之TCP粘包拆包问题解决_java_05

  • lengthFieldOffset=0
  • lengthFieldLength=2
  • lengthAdjustment=0
  • initialBytesToStrip=0

场景二

netty之TCP粘包拆包问题解决_jvm_06

  • lengthFieldOffset=0
  • lengthFieldLength=2
  • lengthAdjustment=0
  • initialBytesToStrip=2

场景三

netty之TCP粘包拆包问题解决_java_07

  • lengthFieldOffset=0
  • lengthFieldLength=2
  • lengthAdjustment=整个包长(14)-长度域的值(14)-长度域偏移(0)-长度域长(2)=-2
  • initialBytesToStrip=0

场景四

netty之TCP粘包拆包问题解决_粘包_08

  • lengthFieldOffset=2
  • lengthFieldLength=3
  • lengthAdjustment=0
  • initialBytesToStrip=0

场景五

netty之TCP粘包拆包问题解决_jvm_09

  • lengthFieldOffset=0
  • lengthFieldLength=3
  • lengthAdjustment=2
  • initialBytesToStrip=0

场景六

netty之TCP粘包拆包问题解决_拆包_10

  • lengthFieldOffset=1
  • lengthFieldLength=2
  • lengthAdjustment=1
  • initialBytesToStrip=3

场景七

netty之TCP粘包拆包问题解决_粘包_11

  • lengthFieldOffset=1
  • lengthFieldLength=2
  • lengthAdjustment=整个包长(16)-长度域的值(16)-长度域偏移(1)-长度域长(2)=-3
  • initialBytesToStrip=3

更多精彩内容关注本人公众号:架构师升级之路

netty之TCP粘包拆包问题解决_java_12


标签:netty,lengthFieldOffset,TCP,粘包,发送,拆包,长度
From: https://blog.51cto.com/u_6784072/6216467

相关文章

  • 长连接Netty服务内存泄漏,看我如何一步步捉“虫”解决
    作者:京东科技王长春背景事情要回顾到双11.11备战前夕,在那个风雨交加的夜晚,一个急促的咚咚报警,惊破了电闪雷鸣的黑夜,将沉浸在梦香,熟睡的我惊醒。一看手机咚咚报警,不好!有大事发生了!电话马上打给老板:老板说:长连接吗?我说:是的!老板说:该来的还是要来的,最终还是来了,快,赶紧先把服......
  • TCP/IP笔记
    OSI模型:(OpenSystemInterconnect)包含七层:物理层,链路层,网络层,传输层,会话层,表示层,应用层TCP/IP协议简化为4层对应OSPI为:应用层:HTTP/FTP/SMTP/Telnet传输层:TCP/UDP网络层:ICMP,IP,IGMP链路层:ARP,RARP应用层: 链路层:交换机MAC-->MAC网络层:IP-->IPMAC表:交换机中MAC和交换机端口对应......
  • golang中通过原始socket实现tcp/udp的服务端和客户端示例
    这些天稍微空点,总结下golang中通过tcp/udp实现服务端客户端的编程实现,毕竟长久以来,如果要截单的http服务,我们直接使用net/http包实现服务,或者使用框架如gin/echo/beego等。以下就直接上代码,稍微看看都能懂起。1.TCP的实现serverpackagemainimport( "bufio" "fmt" "net"......
  • TCP连接状态的多种判断方法
    ​前言在TCP网络编程模型中,无论是客户端还是服务端,在网络编程的过程中都需要判断连接的对方网络状态是否正常。在linux系统中,有很多种方式可以判断连接的对方网络是否已经断开。通过错误码和信号判断通过select系统函数判断通过TCP_INFO套接字选项判断通过SO_KEEPALIVE套接......
  • 一种基于Unix Domain和TCP连接的跨设备多进程间通信的方法
    ​前言:在linux系统进程间通信的方式有消息,消息队列,管道,内存映射,套接字等多种方式。在Android系统上进行进程间通信主要是使用Binder,其它的还有共享内存,管道,RPC和UnixDomain等方式。但是,在linux中常用的消息队列,在Android等系统上并不能直接的使用,Android上常用的Binder,在其他......
  • ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264视频流
    前言:RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力,这里将它们独立出来实现,以便更好的理解协议。本文主要介绍RTSP,RTCP,RTP加载H264数据流。说明:(1)大华IPC摄像头作为服......
  • ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载AAC音频流
    前言:RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力,这里将它们独立出来实现,以便更好的理解协议。本文主要介绍RTSP,RTCP,RTP加载AAC音频流。说明:(1)大华IPC摄像头作为服......
  • QUIC协议 对比 TCP/UDP 协议
    QUIC协议是HTTP3引入的,所以需要了解HTTP的版本迭代。HTTP1.x队头阻塞:下个请求必须在前一个请求返回后才能发出,导致带宽无法被充分利用,后续请求被阻塞(HTTP1.1尝试使用流水线(Pipelining)技术,但先天FIFO(先进先出)机制导致当前请求的执行依赖于上一个请求执行的完成,容易引起队头阻......
  • HTTP和TCP协议的队头阻塞
    队头阻塞(Head-of-lineblocking)其实有两种,一种是 TCP队头阻塞,另一种是 HTTP队头阻塞,而这两者之前其实还存在一定的联系,毕竟 HTTP1/2是建立在TCP协议之上的应用层协议,另外还有HTTP3对队头阻塞的解决。 1、HTTP/1.x的队头阻塞HTTP/1.x有个问题叫队头阻塞,即一个连接同......
  • C++ - TCP通信
    前言socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。如下图:头文件与库:#include<WinSock2.h>​#pragmacomment(lib,"ws2_32.lib")准备工作:创建工程后,首先右键工程,选择属性然后选择C/C++-预......