【必会面试题】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

相关文章

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

Java对接MQTT协议的完整实现示例代码

《Java对接MQTT协议的完整实现示例代码》MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛,:本文主要介绍Ja... 目录前言前置依赖1. MQTT配置类代码解析1.1 MQTT客户端工厂1.2 MQTT消息订阅适配器1.

Linux中的自定义协议+序列反序列化用法

《Linux中的自定义协议+序列反序列化用法》文章探讨网络程序在应用层的实现,涉及TCP协议的数据传输机制、结构化数据的序列化与反序列化方法,以及通过JSON和自定义协议构建网络计算器的思路,强调分层... 目录一,再次理解协议二,序列化和反序列化三,实现网络计算器3.1 日志文件3.2Socket.hpp

Linux中的HTTPS协议原理分析

《Linux中的HTTPS协议原理分析》文章解释了HTTPS的必要性:HTTP明文传输易被篡改和劫持,HTTPS通过非对称加密协商对称密钥、CA证书认证和混合加密机制,有效防范中间人攻击,保障通信安全... 目录一、什么是加密和解密?二、为什么需要加密?三、常见的加密方式3.1 对称加密3.2非对称加密四、

Linux之UDP和TCP报头管理方式

《Linux之UDP和TCP报头管理方式》文章系统讲解了传输层协议UDP与TCP的核心区别:UDP无连接、不可靠,适合实时传输(如视频),通过端口号标识应用;TCP有连接、可靠,通过确认应答、序号、窗... 目录一、关于端口号1.1 端口号的理解1.2 端口号范围的划分1.3 认识知名端口号1.4 一个进程

如何在Spring Boot项目中集成MQTT协议

《如何在SpringBoot项目中集成MQTT协议》本文介绍在SpringBoot中集成MQTT的步骤,包括安装Broker、添加EclipsePaho依赖、配置连接参数、实现消息发布订阅、测试接口... 目录1. 准备工作2. 引入依赖3. 配置MQTT连接4. 创建MQTT配置类5. 实现消息发布与订阅

使用Python进行GRPC和Dubbo协议的高级测试

《使用Python进行GRPC和Dubbo协议的高级测试》GRPC(GoogleRemoteProcedureCall)是一种高性能、开源的远程过程调用(RPC)框架,Dubbo是一种高性能的分布式服... 目录01 GRPC测试安装gRPC编写.proto文件实现服务02 Dubbo测试1. 安装Dubb

SpringBoot快速搭建TCP服务端和客户端全过程

《SpringBoot快速搭建TCP服务端和客户端全过程》:本文主要介绍SpringBoot快速搭建TCP服务端和客户端全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录TCPServerTCPClient总结由于工作需要,研究了SpringBoot搭建TCP通信的过程

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3