【必会面试题】TCP协议的粘包拆包

2024-06-14 13:28

本文主要是介绍【必会面试题】TCP协议的粘包拆包,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

      • TCP数据报文结构
      • 粘包
      • 拆包
      • 如何处理粘包和拆包

TCP协议的粘包和拆包是数据传输过程中常见的现象,它们不是TCP协议本身的设计目的,而是基于TCP协议的特性自然产生的结果。

TCP数据报文结构

字段名English Name长度(比特)描述
源端口号Source Port16发送方的端口号,用于标识发送数据的应用程序
目标端口号Destination Port16接收方的端口号,用于标识接收数据的应用程序
序列号Sequence Number32发送的数据段的第一个字节的序列号,用于数据排序与确认
确认号Acknowledgment Number32确认收到对方的序列号,期望收到的下一个数据段的第一个字节序号
数据偏移(DO)Data Offset4报头长度,指示TCP报头有多少个32位字(单位不是比特),最小值5
保留Reserved6未使用,保留为今后使用,目前应置为0
URGUrgent1紧急指针有效标志
ACKAcknowledge1确认标志,表示确认字段有效
PSHPush1提示接收方应该尽快将数据交付给上层协议处理
RSTReset1复位连接标志,用于异常终止连接
SYNSynchronize1同步序号,用于建立连接
FINFinish1结束标志,表示发送方已经完成数据发送任务,可以关闭连接
窗口大小Window Size16通知对方其接收缓冲区的大小
校验和Checksum16包含TCP报头和数据部分的校验和,用于错误检测
紧急指针Urgent Pointer16只有当URG标志为1时有意义,指向紧急数据的最后一个字节后续的第一个字节
选项与填充Options & Padding可变可选字段,用于扩展功能,如最大报文段大小(MSS)等,不足4字节需填充
  • 一个示例
0000 0000 0000 0001 0000 0000 0000 0010  # 源端口:1,目的端口:2
0000 0000 0000 0000 0000 0000 0000 0001  # 序列号:1
0000 0000 0000 0000 0000 0000 0000 0002  # 确认号:2
0101 0000 0000 0000 0000 0000 0000 0000  # 数据偏移:5,保留:0,控制位:010SYN, ACK0000 0000 0000 0010 0000 0000 0000 0000  # 窗口大小:2
0000 0000 0000 0000 0000 0000 0000 0000  # 校验和:0(示例值,实际计算)
0000 0000 0000 0000 0000 0000 0000 0000  # 紧急指针:0
0000 0000 0000 0000 0000 0000 0000 0000  # 选项:无
0101 0101 0101 0101 0101 0101 0101 0101  # 数据:"Hello"ASCII编码

粘包

概念:在TCP协议中,粘包指的是多个TCP报文段在传输过程中被接收端连续接收,而没有明显的边界区分,看起来就像是一个连续的数据包。这是因为TCP是一个面向流的协议,它不对每个应用层消息单独封装。

  • 假设客户端发送了两个数据包,分别是"Hello"和"World"。在没有粘包的情况下,它们应该分别发送。但在粘包的情况下,它们可能被合并为一个数据包发送。
  1. 客户端发送的数据包1(“Hello”):
0000 0000 0000 0001 0000 0000 0000 0010  # 源端口:1,目的端口:2
0000 0000 0000 0000 0000 0000 0000 0001  # 序列号:1
0000 0000 0000 0000 0000 0000 0000 0002  # 确认号:2
... # 其他TCP头部字段
0101 0101 0101 0101 0101  # 数据:"Hello"ASCII编码
  1. 客户端发送的数据包2(“World”):
0000 0000 0000 0001 0000 0000 0000 0010  # 源端口:1,目的端口:2
0000 0000 0000 0001 0000 0000 0000 0006  # 序列号:6(假设)
0000 0000 0000 0000 0000 0000 0000 0007  # 确认号:7(假设)
... # 其他TCP头部字段
0101 0111 0111 0111 0110 1100  # 数据:"World"ASCII编码

在粘包的情况下,接收方可能收到如下的数据包:

0101 0101 0101 0101 0101 0101 1110 1100  # 数据:"HelloWorld"ASCII编码

拆包

概念:与粘包相反,拆包是指发送端的一个较大的TCP数据包在传输过程中被分割成多个较小的数据包到达接收端。这是因为网络层和传输层(TCP)有各自的MTU(最大传输单元)限制,数据包必须适应这些限制才能在网络中传输。

原因

  • MTU限制:不同网络设备和链路可能有不同的MTU,数据包必须被拆分以适应最小的MTU。
  • 拥塞控制:TCP的拥塞控制机制也可能导致数据被拆分,以减缓发送速度,避免网络拥塞。

如何处理粘包和拆包

TCP协议的粘包拆包可能会导致以下问题:

  1. 数据丢失:在拆包过程中,如果某个分片的数据丢失,可能导致整个原始数据无法还原。
  2. 数据错误:粘包情况下,接收端可能将多个数据包误认为是单个数据包,从而导致数据解析错误。
  3. 性能下降:为了解决粘包拆包问题,需要额外的处理机制,这会增加系统的复杂性和计算开销。

一般解决这些问题有以下几种思路:

  1. 定长包头:在每个数据包前加上固定长度的包头,包头中包含数据包的实际长度。这样,接收端可以根据包头中的长度信息来分割数据包。
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {private final int frameLength;public FixedLengthFrameDecoder(int frameLength) {this.frameLength = frameLength;}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {if (in.readableBytes()< frameLength) {return;}in.markReaderIndex(); // 标记当前读指针位置int dataLength = in.readInt(); // 读取数据长度if (dataLength > frameLength - 4) { // 检查数据长度是否合法in.resetReaderIndex(); // 不合法时重置读指针throw new CorruptedFrameException("data length is larger than the frame length.");}byte[] data = new byte[dataLength];in.readBytes(data);out.add(data);}
}
  1. 分隔符:在数据包之间使用特殊的分隔符作为标识,接收端根据分隔符来分割数据包。这种方法适用于数据包内容不包含分隔符的场景。
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {private static final byte DELIMITER = '#';@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {int readableBytes = in.readableBytes();int readIndex = in.readerIndex();while (readableBytes > 0) {byte nextByte = in.getByte(readIndex);if (nextByte == DELIMITER) {break;}readableBytes--;readIndex++;}if (readableBytes == 0) {return;}int dataLength = readIndex - in.readerIndex();byte[] data = new byte[dataLength];in.readBytes(data);out.add(data);}
}
  1. 长度前缀:在数据包前加上表示数据长度的字段,接收端根据长度字段来分割数据包。这种方法适用于数据包长度可变的情况。
public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder {private static final int LENGTH_FIELD_LENGTH = 4;private static final int LENGTH_FIELD_OFFSET = 0;private static final int LENGTH_ADJUSTMENT = 0;private static final int INITIAL_BYTES_TO_STRIP = LENGTH_FIELD_LENGTH;@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {if (in.readableBytes() < LENGTH_FIELD_LENGTH) {return;}in.markReaderIndex(); // 标记当前读指针位置int dataLength = in.readInt(); // 读取数据长度if (dataLength < 0) { // 检查数据长度是否合法in.resetReaderIndex(); // 不合法时重置读指针throw new CorruptedFrameException("negative length: " + dataLength);}if (in.readableBytes()< dataLength + LENGTH_FIELD_LENGTH) {in.resetReaderIndex(); // 如果可读字节不足以容纳整个数据包,重置读指针return;}in.readerIndex(in.readerIndex() + LENGTH_FIELD_LENGTH); // 跳过长度字段byte[] data = new byte[dataLength];in.readBytes(data);out.add(data);}
}

一般我们通过现有框架如Netty能更方便的解决这些问题。
Netty提供了多种解码器,如FixedLengthFrameDecoder、DelimiterBasedFrameDecoder和LengthFieldBasedFrameDecoder,可以更有效地处理粘包拆包问题。

这篇关于【必会面试题】TCP协议的粘包拆包的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1060533

相关文章

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

微信小程序开发必知必会:文件结构和基本配置

一、微信小程序基本文件结构 1.  project.config.json:项目的基本配置文件,包括项目名称、appid、项目目录、页面文件夹等。     {"setting": {"urlCheck": false,"es6": true,"postcss": true,"nodeModulesPath": "D:\\\\node_modules"},"appid": "wxd678e

【杂记-浅谈DHCP动态主机配置协议】

DHCP动态主机配置协议 一、DHCP概述1、定义2、作用3、报文类型 二、DHCP的工作原理三、DHCP服务器的配置和管理 一、DHCP概述 1、定义 DHCP,Dynamic Host Configuration Protocol,动态主机配置协议,是一种网络协议,主要用于在IP网络中自动分配和管理IP地址以及其他网络配置参数。 2、作用 DHCP允许计算机和其他设备通

【SparkStreaming】面试题

Spark Streaming 是 Apache Spark 提供的一个扩展模块,用于处理实时数据流。它使得可以使用 Spark 强大的批处理能力来处理连续的实时数据流。Spark Streaming 提供了高级别的抽象,如 DStream(Discretized Stream),它代表了连续的数据流,并且可以通过应用在其上的高阶操作来进行处理,类似于对静态数据集的操作(如 map、reduce、

Java线程面试题(50)

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术并且有丰富的Java程序开发、调试、优化经验,所以线程相关的问题在面试中经常会被提到。 在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程,

C语言常见面试题3 之 基础知识

(1)i++和++i哪个效率更高? 对于内建数据类型,二者效率差别不大(去除编译器优化的影响) 对于自定义数据类型(主要是类),因为前缀式(++i)可以返回对象的引用;而后缀式(i++)必须返回对象的值,所以导致在大对象时产生了较大的复制开销,引起效率降低。 (2)不使用任何中间变量如何交换a b的值? void swap(int& a, int& b)//采用引用传参的方式{a^=

Java面试题:内存管理、类加载机制、对象生命周期及性能优化

1. 说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件:Class loader(类装载)、Execution engine(执行引擎)、Runtime data area(运行时数据区)、Native Interface(本地接口)。 Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)装载class文件到Runtim

面试题3:GET 和 POST 有什么区别?

[!]高频面试题。 GET 和 POST 没有本质区别,可以进行相互代替。 1、GET语义:“从服务器获取数据”;POST语义:“往服务器上提交数据”。[设计初衷,不一定要遵守] 2、发请求时,给服务器传递的数据,GET 一般是放在查询字符串中,但GET 也可以把数据放在 body 里。不过比较少见,以至于浏览器不一定能支持,不过其他的http客户端可以支持;POST 一般是放在 body 中

TCP 可靠传输的工作原理

转载地址:https://my.oschina.net/xinxingegeya/blog/485233 感谢原作者 TCP 可靠传输的工作原理 ARQ(Automatic Repeat-reQuest)(自动重传请求) 停止等待ARQ协议 连续ARQ协议   停止等待ARQ协议 全双工通信的双发既是发送方也是接收方。下面为了讨论问题的方便,我们仅考虑A发送数据而B接受数据