Linux网络(十四):TCP协议详解:从“报文”理解到TCP报头与字节流本质,彻底搞懂数据交付与分离
◆ 博主名称 小此方-CSDN博客大家好欢迎来到小此方的博客。⭐️网络系列个人专栏 【主题曲】计算机网络⭐️此方的GitHub github_此方⭐️我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)文章目录概要序論一、重构我们对报文的理解1.1怎么管理呢——先描述再组织1.2所谓的封装和解包本质就是移动 data 指针在缓冲区中的指向1.3回看封包过程与并发思考二、TCP 报头与字节流本质2.1标准问题如何交付、如何分离2.2新问题为什么 TCP 报头中没有报文大小的字段概要序論Hello大家好我是此方。上一篇我们讲解了UDP的深入理解从本文开始我将花费非常非常多的篇幅讲解TCP的底层原理内容。这里也是校招面试的高频考点密集区。理解原理比强记八股更加重要。好我们开始吧。一、重构我们对报文的理解在网络通信中客户端与服务器往往是n : 1 n:1n:1的关系。多个客户端同时向服务器发送报文这意味着在操作系统OS内部必然会同时存在着海量的报文——既然存在海量报文OS 就必须管理这些报文1.1怎么管理呢——先描述再组织操作系统管理报文的核心思路依然是“先描述再组织”在 Linux 内核中通过struct sk_buff结构体来描述报文。结构体内包含了struct sk_buff *next和struct sk_buff *prev指针用以构建报文之间的双向链表连接。由此可以得出核心结论报文 结构体 内存缓冲区。把这个结构抽象出来看sk_buff结构体内部包含head、data、tail、end指针。指针指向的是一块连续的内存数据区分为头空间、二层帧头、IP协议头、TCP/UDP协议头、应用层数据、尾空间。1.2所谓的封装和解包本质就是移动 data 指针在缓冲区中的指向报文在传输时是如何移动指针的答案是± \pm±对应的协议长度。在数据封装时tail指针保持不动data指针向上低地址方向移动对应的协议长度即data - sizeof(struct udphdr)移动后直接使用强转后的data指针写入对应的报头数据例如(struct udphdr*)data-...反之在解包时只需要将data指针向下高地址方向移动剥离协议头即可。你也可以理解为报文在哪一层就在哪一个队列中网络四层结构中对应存在着四个skbuf队列。1.3回看封包过程与并发思考我们回过头来看传统的封包理解应用程序创建缓冲区填入报文⟶ \longrightarrow⟶传输层创建空结构⟶ \longrightarrow⟶应用层将报文拷贝到传输层整个过程和思路板书这里引出一个核心问题如果应用层正在进行报文的解析、处理会不会影响 OS 从网络中读取报文为什么答案是不会因为不论是应用层的数据操作还是系统层的数据接收本质上都是响应硬件中断或时钟中断在逻辑上不分先后。在现代计算机体系中更有 DMA直接内存访问等先进技术能够保证硬件层面 independent、高效地接收网络数据。二、TCP 报头与字节流本质TCP 全称为“传输控制协议”Transmission Control Protocol。人如其名它的核心职责就是要对数据的传输进行一个详细且精确的控制。从系统的角度来看 TCP 传输以我们以前学习的文件系统为例文件struct file有一个缓冲区我们调用写入操作时是向缓冲区写入而不是直接向外设写入。TCP 传输也是同样的道理write系统调用只需要往缓冲区写入不需要阻塞等待报文被发送到目标而是可以直接继续执行应用层的工作。我们可以把报文发送理解为“一次比较复杂的刷新”。这样可以显著提升效率。为了深入理解 TCP 是如何控制数据传输的我们必须先理清协议设计中的基本问题。2.1标准问题如何交付、如何分离这是TCP报文的格式抽象图:网络协议在设计时必须回答两个最核心的机制问题报头和有效载荷如何分离如何交付关于如何交付直接提取 TCP 报头中的16位目的端口号操作系统就知道该把数据交付给哪一个应用层进程了。那么报头和有效载荷又是如何分离的呢关键就在于报头中的4位首部长度。4 位二进制能表示的数字范围是 0000~1111即 0~15。如果一个数字代表 1 个字节那么首部最大只能表示 15 个字节这显然是不够用的。因此我们约定基本单位一定是 4 个字节即 1 个数值代表 4 个字节。同时报文必须是 4 字节的整数倍。由此可以推算出 TCP 报头的长度逻辑报头大小的理论范围是 0~60 字节15 × 4 15 \times 415×4。但 TCP 标准报头至少有固定的20 个字节再加上可变长度的选项部分。所以报头实际大小范围被缩小为20~60 字节。映射到 4 位首部长度的二进制取值就是0101~1111即 5~15。此外选项的长度也必须是 4 字节的倍数。有了这些知识报头与有效载荷的分离流程就非常清晰了首先固定读取报头的前 20 个字节。提取出其中的 4 位首部长度x xx。计算得到报头总长度为x × 4 x \times 4x×4。通过公式4 × x − 20 4 \times x - 204×x−20计算出选项的长度。剥离掉计算出的总报头长度剩下的全部就是有效载荷应用层数据了。2.2新问题为什么 TCP 报头中没有报文大小的字段有人可能会问会不会出现上面的有效载荷和下一个报头混在一起的情况答案是不存在因为在系统的底层我们的每一个报文都是一个独立的sk_buf至于为什么 TCP 报头不需要设计“报文大小”或“数据长度”的字段这源于 TCP 的核心设计理念TCP 是面向字节流的传输层协议它只负责将字节序列可靠、有序地送达接收缓冲区。TCP 不关心也不理解应用层业务报文的边界即无“报文界限”因此首部无需设置“应用层报文大小”字段。应用层数据的拆分与拼装粘包/半包处理完全需要由应用层协议自行定义和解析。不关心职责分工TCP 是传输层协议它的唯一使命是保障字节能不丢包、不乱序地送达对方。至于这些字节代表的是一张图片、一段 JSON 还是一个 HTTP 请求TCP 不需要也不应该去干涉。不理解数据结构TCP 看到的只有 0101 的二进制字节流。它不知道哪个字节是消息的开头Header哪个字节是消息的结尾Footer。好的本期内容就到这里如果对你有帮助还不要忘记点赞三联支持。我是此方我们下期再见。bye!