最近在做优化的时候涉及到了这块内容,觉得值得写下来,方便以后翻阅。
🔥个人主页:Cx330🌸
❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》
《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔
《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战
🌟心向往之行必能
🎥Cx330🌸的简介:
目录
前言:
在现代计算机网络体系中,传输控制协议(Transmission Control Protocol,简称 TCP)是构建互联网服务(如 Web 服务、数据库交互、远程连接等)的最核心基石之一。与 UDP 协议的“发后即忘”不同,TCP 被设计为一个面向连接的、可靠的、基于字节流的传输层协议。
这里将依据 TCP 协议的核心要点,逐层深入剖析:从 TCP 在 Linux 体系中的定位、报头的结构设计与 Linux 内核源码实现,再到 TCP 实现“绝对可靠性”的核心保障机制。
一. TCP 的简单回顾
1.1 TCP 在网络分层中的位置
在标准的 TCP/IP 四层(或 OSI 七层)模型中,TCP 位于传输层(Transport Layer):
- 应用层(Application Layer):HTTP、SSH、DNS 等协议,负责具体的业务逻辑。
- 传输层(Transport Layer):TCP、UDP,负责端到端(Process-to-Process)的数据传输服务。
- 网络层(Network Layer):IP 协议,负责主机到主机(Host-to-Host)的路由与寻址。
- 链路层(Link Layer):以太网、WiFi 等,负责局域网内物理帧的传输。
socket(), bind(), connect(), write(), read() 等)。
1.2 TCP 数据发送的本质
许多初学者容易产生一个误区:觉得调用 write() 或 send() 函数就是把数据直接发到了物理网线上。这种理解是不准确的。
TCP 数据发送的本质是:数据拷贝(Buffer Copy)。实际上,我们向网络发送数据的本质是将数据拷贝到操作系统内核的 TCP 发送缓冲区中。
应用程序 write() → 拷贝数据到TCP发送缓冲区 → TCP协议栈控制发送时机、速率 → 网络
1. 发送端:应用程序调用 write() 时,操作系统只是将数据从用户态缓冲区(User Buffer)拷贝到 Linux 内核为该 Socket 分配的发送缓冲区(Send Buffer)中。
2. 内核调度:内核 TCP 协议栈会根据当前的拥塞状况、滑动窗口大小以及 MTU/MSS 限制,自主决定何时、将多少数据打包成 TCP 报文段(Segment)递交给 IP 层。
3. 接收端:网卡收到数据帧后触发中断,内核将 TCP 报文解析并存入 Socket 的接收缓冲区(Receive Buffer)。应用程序调用 read() 时,再从内核接收缓冲区拷贝数据到用户态。
所以,TCP 的“面向字节流”特性,本质上就是内核通过发送与接收缓冲区对数据流进行的解耦与动态调度。
二. TCP 协议格式深度解析
2.1 TCP 报头整体结构
为了实现复杂的控制逻辑(如序号管理、流量控制、状态切换等),TCP 报头设计了丰富的字段。标准 TCP 报头不包含选项时长度为 20 字节,加上可选项后最大可达 60 字节。
其标准格式示意图如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口号 (16 bits) | 目的端口号 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序号 (Sequence Number, 32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认序号 (Acknowledgment Number, 32 bits)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 首部长度 | 保留 |U|A|P|R|S|F| |
| (4 bits) |(6bit)|R|C|S|S|Y|I| 窗口大小 (Window, 16 bits) |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 (Checksum, 16 bits) | 紧急指针 (Urgent Pointer, 16 bits)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 (Options, 0 - 40 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据部分 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.2 核心字段详解
2.2.1 源 / 目的端口号(16 位)
- 作用:建立进程到进程的映射。
- 大小:16 bits(对应端口范围 0 - 65535)。
- 逻辑:IP 地址标识互联网上唯一的主机,而端口号则标识该主机上唯一的网络应用进程。两者结合构成套接字(Socket Pair)。
2.2.2 32 位序号与确认序号
TCP 将发送的字节流中的每一个字节都编上了一个序号(Sequence Number)。
- 32 位序号(Seq):表示本报文段所发送的数据的第一个字节的序号。
- 32 位确认序号(Ack):接收端发回的确认,表示接收端期望下一次收到的字节序号。这意味着:序号为 Ack - 1 及之前的所有字节数据都已经成功且连续地接收到了(累积确认)。
例如:A 发送了序号为 1~1000 的字节数据,B 收到后会回复确认序号为 1001 的 ACK 报文。
2.2.3 4 位首部长度(重点)
这是 TCP 报头设计中最精妙的字段之一,也是很多面试的高频考点。
- 为什么需要这个字段?
字段规则:
- 4 位二进制范围:0000~1111(十进制 0~15)
- 基本单位:4 字节
- 实际首部长度 = 字段值 × 4 字节
- 取值范围:
- 最大值:15(15×4=60 字节),表示选项部分占满 40 字节
- 为什么要以 4 字节为单位?因为 4 位最多只能表示 15 个值,直接表示字节长度的话最多只能到 15 字节,远远不够。通过规定基本单位为 4 字节,将表达范围从 0~15 字节扩展到了 0~60 字节。
- 更深层次的原因是:TCP 报头长度必须是 4 字节的整数倍,所以报头长度的二进制表示的低两位永远是 0。我们不需要存储这两位,只需要存储高 4 位即可,读取时再左移两位(×4)补回低两位的 0。
- 标准报头 20 字节:20 ÷ 4 = 5 → 字段值为 5(二进制 0101)
- 带 12 字节选项的报头:20+12=32 字节 → 32 ÷ 4 = 8 → 字段值为 8(二进制 1000)
2.2.4 6 位标志位(Control Flags)
标志位用于控制 TCP 的状态变迁与报文属性,每一位占用 1 bit:
1. URG (Urgent):紧急指针有效标记。表明本报文中包含高优先级紧急数据(与 16 位紧急指针结合用)。
2. ACK (Acknowledgment):确认号有效标记。TCP 规定连接建立后,发出的所有报文段都必须将 ACK 置 1。
3. PSH (Push):提示接收方应尽快将接收缓冲区的数据交付给应用层,而不是等待缓冲区填满。
4. RST (Reset):复位标记。表示连接出现严重异常(如对端断电、非法连接),要求强制重置或关闭连接。
5. SYN (Synchronize):同步序号标记。在建立连接(三次握手)时用于同步初始序号(ISN)。
6. FIN (Finish):结束标记。表示发送方已无数据发送,请求释放连接(四次挥手)。
2.3 Linux 内核中 TCP 报头的实现
在 Linux 内核源码(如 <netinet/tcp.h> 或 <linux/tcp.h>)中,TCP 报头是通过结构体 struct tcphdr 定义的。
为了应对大端序(Big-Endian,即网络字节序)与小端序(Little-Endian,即主机字节序)的差异,内核用条件编译对位域进行了处理:
struct tcphdr {
__be16 source; /* 源端口号 (16位,网络字节序) */
__be16 dest; /* 目的端口号 (16位,网络字节序) */
__be32 seq; /* 32位序号 */
__be32 ack_seq; /* 32位确认序号 */
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4, /* 保留位 */
doff:4, /* 首部长度 (Data Offset, 4 bits) */
fin:1, /* FIN 标志 */
syn:1, /* SYN 标志 */
rst:1, /* RST 标志 */
psh:1, /* PSH 标志 */
ack:1, /* ACK 标志 */
urg:1, /* URG 标志 */
ece:1, /* ECN Echo */
cwr:1; /* Congestion Window Reduced */
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4, /* 首部长度 */
res1:4, /* 保留位 */
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your defines"
#endif
__be16 window; /* 16位接收窗口大小 */
__sum16 check; /* 16位校验和 */
__be16 urg_ptr; /* 16位紧急指针 */
};
源码解读:
- 位段的用:TCP 报头中有很多单个位的标志,使用 C 语言的位段特性能够节省内存,同时方便按位操作。
- 大小端适配:由于不同 CPU 架构的字节序不同,内核通过条件编译来适配小端和大端模式下的位段顺序。
- 网络字节序:所有多字节字段(如
source、dest、seq等)都使用__be16或__be32类型,表示网络字节序(大端)。
2.4 TCP 与 UDP 报头的关键区别
比较维度
TCP 报头
UDP 报头
报头长度
可变长度(20 ~ 60 字节)
固定长度(8 字节)
复杂程度
结构复杂,包含各种控制状态与序号字段
结构极简,仅含端口、长度和校验和
连接与状态
面向连接,报头包含状态同步位(SYN/FIN/RST)
无连接,无状态标记
可靠性保证
包含 Seq、Ack 字段,原生支持重传与乱序重排
无 Seq/Ack,不保障可靠性与顺序
流量与拥塞控制
包含 16 位滑动窗口字段,支持动态调节
无流量控制信息
三. TCP 可靠性核心:确认应答与超时重传
TCP 能够在不可靠的 IP 网络层之上提供 100% 可靠的数据传输服务,其核心底座正是 确认应答(ACK) 与 超时重传(Timeout Retransmission) 机制。
3.1 可靠性的本质
在物理世界与网络传输中,不存在 100% 不丢包、不损坏的物理介质。因此,可靠性的本质不是“保证数据永远不丢”,而是“能够准确感知数据是否丢失,并在丢失时进行补救,确保最终交付的数据正确且完整”。
TCP 实现这一点的金科玉律是:发送方发出的每一个数据包,都需要收到接收方的明确应答;若未收到应答,发送方必须有能力重新发送。
```
发送方 接收方
| |
| -------- 数据 (Seq=1) -------> |
| | (成功收到)
| | (收到 1~500)
| | (收到 501~1000)
| |
| |
| x--- ACK: Ack=501 (丢失) ---|
| (定时器超时 RTO) |
| -- Seg1: Seq=1 (重传) ----------> | (去重,重新发送 ACK)
|
以上就是这次整理的全部内容,希望对你有所启发。如果有不同见解,欢迎在评论区交流讨论。
评论 (0)
暂无评论