目录
[1. TCP的核心定位与控制本质](#1. TCP的核心定位与控制本质)
IP网络本身提供的是不可靠传输服务,数据在传输过程中可能出现重复 (同一数据收到多次)、乱序 (后发的先到)、丢包(数据中途丢失)等问题,TCP协议正是为了解决这些不可靠问题而设计的,通过序号去重和排序、超时重传、确认应答等机制,在不可靠的IP层之上构建可靠的传输服务
1. TCP的核心定位与控制本质

控制本质 :TCP是一个传输控制协议 ,其"控制"体现在自主决定数据的发送时机、发送量以及出错后的处理方式
全双工通信 :一个TCP连接对应一个文件描述符,在内核中维护一对缓冲区(发送缓冲区和接收缓冲区),支持同时读写
系统调用本质: write、read、send、recv等接口的本质是拷贝函数,负责在用户级缓冲区和内核级缓冲区之间拷贝数据,而非直接发送数据到网络
全双工与半双工
全双工是指在通信的任意时刻,数据可以在两个方向上同时进行传输,互不干扰。就像一条双向四车道的高速公路,车辆可以同时向两个方向行驶
TCP协议支持全双工通信,这意味着:通信双方可以同时发送和接收数据,发送和接收操作互不阻塞,每个方向都有独立的流量控制和确认机制
半双工是指在通信的任意时刻,数据只能在一个方向上传输,不能同时双向传输。就像对讲机一样,一方说话时另一方只能听,必须等对方说完才能发言关键特征: 通信双方可以双向传输 数据,但不能同时进行 ,需要切换传输方向 ,存在切换开销,同一时刻只有一个方向的数据流
| 特性 | 半双工 | 全双工 |
|---|---|---|
| 传输方向 | 单向交替传输 | 双向同时传输 |
| 实时性 | 较差,需要等待 | 好,无等待 |
| 效率 | 低(方向切换开销) | 高 |
| 实现复杂度 | 简单 | 复杂 |
| 硬件成本 | 低(如单根信号线) | 高(如双绞线、光纤) |
| 典型应用 | 对讲机、RS-485总线 | 电话、以太网 |
2.报文结构
报文组成 :TCP报文由标准报头(20字节) 、选项 和有效载荷组成

报头关键字段
- 源/目的端口号(各16位):标识通信的进程(数据是从哪个进程来, 到哪个进程去)
- 32位序号 / 32位确认序号:用于保证数据的按序到达、去重以及确认应答
- 4位首部长度:以4字节为单位,表示报头(含选项)的总长度,用于分离报头和有效载荷
- 16位窗口大小:用于流量控制,表示本端接收缓冲区剩余的可用空间
- 6个标记位:用于区分报文类型(如SYN建立连接、ACK确认、FIN断开连接、RST重置连接等)
- 报头与有效载荷如何分离然后交付给上层
TCP通过固定20字节的标准报头(其中"4位首部长度"字段以4字节为单位指明整个报头的总长度)来实现报头与有效载荷的分离,内核解析时先读取固定长度的基础报头,再根据该字段计算出选项区的长度,从而精准定位到有效载荷的起始位置,完成解包并向上层交付
- 注意事项
TCP通信中,无论是数据报文、纯ACK报文,还是SYN、FIN等控制报文,都必须携带完整的TCP报头,因为报头中的端口号、序号、确认序号、窗口大小、标志位和校验和等信息是保证连接状态维护、数据可靠性、流量控制和报文正确性所必需的,缺一不可
16位窗口大小与流量控制
rwnd(接收窗口)是接收方通过 TCP 报头中 16 位窗口字段通告给发送方的流量控制窗口大小
由于 16 位字段最大只到 65535,TCP 使用窗口缩放选项(Window Scaling) 扩展:
| 概念 | 说明 |
|---|---|
| 报头中的 16 位值 | 原始值(0 ~ 65535) |
| 缩放因子 | 握手时协商(0 ~ 14),表示左移位数 |
| 实际 rwnd | 报头值 << 缩放因子(最大约 1GB) |
16位窗口大小是TCP报头中用于流量控制的核心字段,它表示本端接收缓冲区的剩余空间大小 ,取值范围0-65535字节,发送方根据这个值动态调整发送速度,确保不会超过接收方的处理能力,从而实现发送速率与接收速率的匹配
流量控制是TCP根据接收方的处理能力动态调整发送速率的机制,通过窗口大小字段告知发送方还能接收多少数据,使发送方能够稳定地按量发送,避免因发送过快导致接收方缓冲区溢出而丢包,确保数据能够平稳地从发送端传输到接收端如果不做流量控制 ,发送方盲目发送数据会导致接收方缓冲区溢出而大量丢包,进而引发频繁的超时重传 ,这些被丢弃的数据已经占用 了路由器的缓存、链路的带宽和CPU处理资源 ,重传又再次占用同样的资源,形成双重浪费,因此流量控制不是技术上能不能做的问题,而是资源利用上好不好、优不优的问题
保留字段与校验和字段(了解)
保留字段是TCP报头中一个6位的预留字段,目前必须全部置为0,供未来协议扩展使用
校验和是TCP报头中一个16位的字段,用于检测TCP报文在传输过程中是否发生损坏
32位序号与32位确认序号
序号(本报文数据位置) 确认序号(对方数据接收进度)
↓ ↓
保证按序到达 实现累积确认,允许ACK少量丢失
↓ ↓
└──────────┬───────────────────┘
↓
捎带应答成为可能
(一个报文同时做两件事)
↓
提高传输效率,减少报文数量
序号能够保证一批一批数据的按序到达。 发送方为每个数据块分配唯一的序号,接收方根据序号对乱序到达的数据进行重排序,确保最终交付给应用层的数据与发送顺序一致,从而解决了IP网络固有的数据乱序问题
TCP序号的本质是字节流的编号,每个字节都被赋予一个序号
(1)序号的本质:发送数据块的第一个字节的序号
// 正确的定义
struct tcp_sequence_correct {
* 例如:
* - 初始序号为 1000
* - 发送 100 字节数据
* - 数据占用序号:1000-1099
* - 本报文的序号 = 1000(第一个字节)
* - 下一个待发送的序号 = 1100
*/
u32 seq; // 当前报文第一个字节的序号
};
(2)通过首尾字节标识数据
// 发送方发送数据块
void tcp_send_data_chunks() {
/*初始序号 = 1000
*
* 数据块1:发送100字节
* 序号 = 1000(数据占用序号1000-1099)
*
* 数据块2:发送200字节
* 序号 = 1100(数据占用序号1100-1299)
*
* 数据块3:发送50字节
* 序号 = 1300(数据占用序号1300-1349)
*/
}
// 序号与数据的映射
struct data_mapping {
u32 start_seq; // 数据起始序号
u32 end_seq; // 数据结束序号 = start_seq + len - 1
void *data; // 实际数据内容
};
(2)确认序号的计算规则
/*
* 确认序号 = 已收到的最大连续字节序号 + 1
*
* 具体场景:
* 1. SYN报文:确认序号 = seq + 1(SYN占用1个序号)
* 2. FIN报文:确认序号 = seq + 1(FIN占用1个序号)
* 3. 普通数据报文:确认序号 = seq + len
* 4. 纯ACK报文:
* 纯ACK本身不携带数据,其确认序号反映的是接收方的数据接收进度。
* 收到能扩展连续区间的数据时增长,收到重复或乱序数据时保持不变。
*/
意义:确认序号之前的所有数据都已收到,下一次发送请从确认序号开始
序号消耗规则
| 报文段 | 是否消耗序号 | 原因 |
|---|---|---|
| SYN | 消耗1 | 连接建立需要可靠的序号同步 |
| FIN | 消耗1 | 关闭连接需要可靠的结束通知 |
| RST | 不消耗 | 异常终止不需要可靠性保证 |
| ACK | 不消耗 | 纯确认不携带数据 |
| 普通数据 | 按字节数消耗 | 每个字节对应一个序号 |
(2)确认序号的累积确认特性
累计确认是指确认序号表示该序号之前的所有数据都已经成功收到,接收方不需要对每个报文单独确认,一个ACK可以确认多个报文
void cumulative_acknowledgment() {
/*
* 场景:发送方发送了3个数据块
* 数据块1: seq=1000, len=100 → 占用1000-1099
* 数据块2: seq=1100, len=200 → 占用1100-1299
* 数据块3: seq=1300, len=50 → 占用1300-1349
*
* 情况1:只收到数据块1
* ACK = 1100 → 确认1000-1099已收到
*
* 情况2:收到数据块1和数据块3(数据块2丢失)
* ACK = 1100 → 还是只确认到1100
* 因为数据块2(1100-1299)没有收到,确认序号不会跳跃到1300
*
* 情况3:收到所有数据块
* ACK = 1350 → 确认所有数据(1000-1349)都已收到
*/
}
确认序号允许应答有少量的丢失。 由于确认序号是累积的,只要后续的ACK 到达,就能覆盖之前丢失的ACK。例如ACK1丢失但ACK2到达,ACK2确认了更大范围的数据,自动包含了ACK1的确认信息,因此少量的ACK丢失不会影响可靠传输
如果前面的报文丢了,即使后面的收到了,ACK确认序号只能是前面的那个,这保证了确认序号是线性的,不会跳跃,进而使滑动窗口连续滑动
需要有两个序号是为了:捎带应答时既是应答又能携带数据。 两个序号字段------一个标识本报文携带的数据位置,另一个确认对方的数据接收进度------使得一个报文可以同时完成数据发送和数据确认两项任务,避免了为每个ACK单独发送一个报文,大大提高了传输效率
标记位
TCP通过标准6个标记位(SYN、ACK、FIN、RST、PSH、URG)的不同组合来区分报文类型(如连接请求、确认、关闭、重置等),从而决定接收方的处理机制
| 标记位 | 全称 | 长度 | 功能说明 | 典型使用场景 |
|---|---|---|---|---|
| SYN | Synchronize | 1 bit | 请求建立连接,同步序号 | 三次握手第一步(客户端 → 服务端) |
| ACK | Acknowledgment | 1 bit | 确认序号有效 | 除第一个SYN报文外,几乎都置1 |
| FIN | Finish | 1 bit | 通知对端本端关闭连接 | 四次挥手,主动关闭方发送 |
| RST | Reset | 1 bit | 强制终止 连接 | 连接异常、端口未打开、超时、半开连接 |
| PSH | Push | 1 bit | 提示接收端立即从缓冲区取走数据 | 实时交互数据、telnet、ssh |
| URG | Urgent | 1 bit | 紧急指针有效 | 发送带外数据(OOB),如中断指令 |
| 报文段 | 标志位 | 作用 | 是否携带数据 | 是否消耗序号 |
|---|---|---|---|---|
| 复位报文段 | RST=1 |
异常终止连接、拒绝非法请求 | 通常无 | 不消耗 |
| 同步报文段 | SYN=1 |
建立连接、协商初始序号 | 可选(如选项字段) | 消耗1个 |
| 结束报文段 | FIN=1 |
正常关闭连接、表示数据发送完毕 | 通常无 | 消耗1个 |
连接重置(RST)常见原因:
- 连接请求端口未开放(如 connect 到未监听端口);
- 半开连接(一方崩溃后对端发送数据);
- 收到不属于任何连接的报文;
- 超时未收到响应,强制复位;
- 应用层调用 setsockopt 带 SO_LINGER 且 timeout=0
tcp保证可靠性,但是tcp允许连接建立失败
紧急指针 / URG
- TCP 紧急数据默认最多 1 字节(标准实现中)
- 通过 send(..., MSG_OOB) 发送
- 场景:服务器长时间繁忙,客户端发送 Ctrl+C / 中断 等高优先级指令
服务端管理连接的成本(描述 + 组织)
- 内核或应用层会用结构体(如 struct tcp_sock / struct socket)描述每个连接,包含:状态(ESTABLISHED, FIN_WAIT1...),序号、确认号,发送/接收缓冲区,定时器(重传、保活、时间等待),拥塞控制参数
- 这些结构体被组织在哈希表、连接表、队列中,维护开销包括内存、CPU、定时器管理
3.基于TCP应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
当然, 也包括用户写TCP程序时自定义的应用层协议