TCP 协议
TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传 输进行一个详细的控制;
TCP 协议段格式

• 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
• 32 位序号/32 位确认号: 后面详细讲;
• 4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以 TCP 头部最大长度是 15 * 4 = 60
• 6 位标志位:
○ URG: 紧急指针是否有效
○ ACK: 确认号是否有效
○ PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
○ RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段
○ SYN: 请求建立连接; 我们把携带 SYN 标识的称为同步报文段
○ FIN: 通知对方, 本端要关闭了, 我们称携带 FIN标识的为结束报文段
• 16 位窗口大小: 后面再说
• 16 位校验和: 发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此 处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分.
• 16 位紧急指针: 标识哪部分数据是紧急数据;
• 40 字节头部选项: 暂时忽略
TCP 首部格式详解
TCP 首部长度最小为 20 字节 (没有选项时),最大为 60 字节(选项占 40 字节)。下图是每个字段的位置:
text
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口 | 目的端口 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序号 (Sequence Number) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认序号 (Acknowledgment Number) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 首部长度 | 保留 |U|A|P|R|S|F| 窗口大小 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 | 紧急指针 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 (可选) | 填充 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1. 源端口(16 位)& 目的端口(16 位)
-
作用 :标识发送方和接收方的应用进程(通过端口号找到对应的 socket)。
-
细节:端口号加上 IP 首部中的源 IP 和目的 IP,才能唯一确定一个 TCP 连接(四元组)。
-
常用范围:0~1023 为系统保留(知名端口),1024~49151 为注册端口,49152~65535 为动态/私有端口。
2. 序号(32 位,Sequence Number)
-
作用 :该 TCP 报文段中第一个数据字节的编号。
-
初始序号(ISN):建立连接时双方随机生成(避免历史报文干扰),而非固定从 1 开始。
-
范围:0 ~ 2^32 - 1,到达最大值后回绕到 0(TCP 通过时间戳或扩展选项处理回绕问题)。
-
为什么重要:保证数据有序、去重、检测丢失。
3. 确认序号(32 位,Acknowledgment Number)
-
作用 :期望收到的下一个字节的序号,同时隐含确认了该序号之前的所有字节都已正确收到。
-
有效条件 :仅当 ACK 标志位 = 1 时,确认序号字段才有效。
-
累计确认:Ack = N 表示序号 N-1 及之前的数据都已收到。
-
示例:收到 Seq=1001 且长度=500 的数据后,回复 Ack=1501(表示期待第 1501 字节)。
4. 首部长度(4 位)
-
作用 :表示 TCP 首部有多少个 32 位字(4 字节)。
-
取值范围:5 ~ 15(因为最小 20 字节,即 5×4=20;最大 60 字节,即 15×4=60)。
-
为什么需要:因为选项字段长度可变,接收方需要知道首部在哪里结束、数据从哪里开始。
5. 保留(6 位)
- 作用:保留为未来使用,当前必须置 0。
6. 标志位(每个 1 位,共 6 位)
| 标志 | 名称 | 为 1 时的含义 |
|---|---|---|
| URG | Urgent | 紧急指针字段有效,报文中有紧急数据(应优先处理) |
| ACK | Acknowledgment | 确认序号字段有效(除初始 SYN 报文外,几乎所有报文都置 1) |
| PSH | Push | 提示接收端立即将数据交给应用层,不要等缓冲区满 |
| RST | Reset | 连接出现严重异常,需要强制关闭并重新建立连接(拒绝非法请求) |
| SYN | Synchronize | 建立连接时使用:SYN=1 表示这是一个连接请求或连接接受报文 |
| FIN | Finish | 关闭连接时使用:发送方不再发送数据 |
补充细节:
PSH 的实际行为:发送方设置 PSH 后,接收方 TCP 不会等待缓冲区填满,而是立即把数据递交给应用进程(但现代 TCP 实现通常自动优化,很少显式依赖)。
RST 常见场景:尝试连接一个未监听的端口、连接超时、收到不属于现存连接的报文。
SYN+ACK:服务器回复连接请求时,SYN=1, ACK=1。
7. 窗口大小(16 位)
-
作用 :告诉对方从确认序号开始,自己还能接收多少字节的数据(即接收窗口大小)。
-
范围 :0 ~ 65535 字节。若需要使用更大窗口,可以通过 TCP 窗口缩放选项(Window Scale)将窗口值左移若干位(最大可达 1GB)。
-
用途 :实现流量控制,防止发送方发送过快导致接收方缓冲区溢出。
-
动态变化:接收方根据自身可用缓冲区大小随时调整窗口值并通知发送方。
8. 校验和(16 位)
-
作用:检测 TCP 首部和数据在传输过程中是否出现比特错误。
-
计算范围 :TCP 伪首部(12 字节,包含源 IP、目的 IP、协议号、TCP 长度)+ TCP 首部 + TCP 数据。
- 伪首部并不真正传输,只在计算校验和时临时构造。
-
计算方式:将上述所有内容按 16 位字累加,进位回卷,最后取反码。
-
接收方:同样计算,若结果不为全 1(即 0xFFFF)则丢弃报文。
为什么包含伪首部:为了验证报文是否确实发送给了正确的 IP 和协议(防止 IP 欺骗或路由错误)。
9. 紧急指针(16 位)
-
作用 :仅在 URG=1 时有效,指向紧急数据的最后一个字节的序号(偏移量,相对于当前序号字段的值)。
-
用途:发送紧急数据(如中断命令)时,接收方可以立即读取,而不被流控阻塞。
-
实际使用:现代应用很少依赖,因为带外数据(out-of-band data)在其他机制下可能更复杂。
10. 选项(长度可变,最多 40 字节)
-
常见选项:
-
MSS (Maximum Segment Size):告诉对方自己能接收的最大报文段长度(不包括 TCP 首部)。
-
窗口缩放因子:用于扩展窗口大小(Windows Scale)。
-
时间戳:用于计算 RTT 和防止序号回绕(PAWS)。
-
SACK (Selective Acknowledgment):允许接收方告知哪些数据块丢失(提高重传效率)。
-
-
填充:确保首部长度是 4 字节的整数倍(用 0 填充)。
总结:各字段的功能分组
| 功能 | 相关字段 |
|---|---|
| 标识进程 | 源端口、目的端口 |
| 可靠传输(有序/确认) | 序号、确认序号、ACK、SYN、FIN |
| 流量控制 | 窗口大小 |
| 错误检测 | 校验和 |
| 紧急数据 | URG、紧急指针 |
| 连接控制 | SYN、FIN、RST |
| 提示接收方 | PSH |
| 扩展功能 | 选项、保留、首部长度 |
确认应答(ACK)机制
-
主机A:发送方
-
主机B:接收方
-
数据(1~1000) :表示该TCP报文段携带的数据字节序号是从 1 到 1000。
-
确认应答(下一个是1001):主机B收到数据后回复的确认报文,含义是 "我已收到 1~1000 的所有数据,下一个期望收到的字节序号是 1001"。
-
数据(1001~2000):主机A收到确认后,继续发送下一个数据段,字节序号从 1001 到 2000。
-
确认应答(下一个是2001):主机B再次回复,表示成功收到 1001~2000,期待序号 2001。
(1)序列号与累计确认
-
TCP 把数据流看作一个字节流,每个字节都有一个唯一的序列号(Seq)。
-
主机A发送的第一个数据段"1~1000"实际上是指该数据段起始字节序号为 1,长度为 1000 字节。
-
主机B回复的确认号(Ack)是 1001 ,这是 TCP 的 累计确认 机制:告诉主机A "我已经收到了序号 1000 及之前的所有字节,请从 1001 开始发送"。
(2)确认应答保证可靠
-
主机A每发一个数据段,都需要收到来自主机B的确认。
-
如果一段时间内没有收到确认(超时),主机A会重传未确认的数据。
-
图中每个数据段都得到了确认,因此主机A可以正常发送下一段。
(3)发送与接收的同步
-
主机A不会一次性把所有数据全部发出去,而是等待对方确认后再发下一段(这是最简单的 停-等协议 的体现)。
-
在实际 TCP 中,为了提高效率,会使用 滑动窗口 允许连续发送多个数据段再等待确认,但图里展示的是基础逻辑:发送 → 确认 → 再发送。
TCP 将每个字节的数据都进行了编号. 即为序列号.

每一个 ACK 都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下 一次你从哪里开始发
超时重传机制

• 主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机 B;
• 如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发
但是, 主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;

因此主机 B 会收到很多重复数据. 那么 TCP 协议需要能够识别出那些包是重复的包, 并 且把重复的丢弃掉. 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
那么, 如何超时的时间如何确定?
• 最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返 回".
• 但是这个时间的长短, 随着网络环境的不同, 是有差异的.
• 如果超时时间设的太长, 会影响整体的重传效率;
• 如果超时时间设的太短, 有可能会频繁发送重复的包
TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超 时时间
• Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控 制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
• 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
• 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
• 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接
连接管理机制
在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接
三次握手(建立连接)
| 当前状态 | 事件 / 动作 | 下一状态 | 说明 |
|---|---|---|---|
| CLOSED | 服务器执行 listen() |
LISTEN | 服务器被动监听 |
| CLOSED | 客户端执行 connect() |
SYN_SENT | 客户端发送 SYN |
| LISTEN | 收到客户端的 SYN | SYN_RCVD | 服务器回复 SYN+ACK |
| SYN_SENT | 收到服务器的 SYN+ACK | ESTABLISHED | 客户端回复 ACK |
| SYN_RCVD | 收到客户端的 ACK | ESTABLISHED | 连接建立完成 |
四次挥手(关闭连接)
关键原则 :主动调用
close()的一方为主动关闭方 ,另一方为被动关闭方。下面分别列出两种场景。
场景 A:客户端主动关闭(服务器被动关闭)
| 角色 | 当前状态 | 事件 / 动作 | 下一状态 | 说明 |
|---|---|---|---|---|
| 客户端(主动) | ESTABLISHED | 调用 close(),发送 FIN |
FIN_WAIT_1 | 主动发起关闭 |
| 服务器(被动) | ESTABLISHED | 收到 FIN,回复 ACK | CLOSE_WAIT | 应用层会收到 EOF |
| 客户端 | FIN_WAIT_1 | 收到 ACK(对 FIN 的确认) | FIN_WAIT_2 | 等待服务器发送 FIN |
| 服务器 | CLOSE_WAIT | 应用层处理完数据后调用 close(),发送 FIN |
LAST_ACK | 主动发送 FIN |
| 客户端 | FIN_WAIT_2 | 收到服务器的 FIN,回复 ACK | TIME_WAIT | 进入 2MSL 等待 |
| 服务器 | LAST_ACK | 收到客户端对 FIN 的 ACK | CLOSED | 彻底关闭 |
| 客户端 | TIME_WAIT | 等待 2MSL 后 | CLOSED | 防止残留报文干扰 |
场景 B:服务器主动关闭(客户端被动关闭)
| 角色 | 当前状态 | 事件 / 动作 | 下一状态 | 说明 |
|---|---|---|---|---|
| 服务器(主动) | ESTABLISHED | 调用 close(),发送 FIN |
FIN_WAIT_1 | 主动发起关闭 |
| 客户端(被动) | ESTABLISHED | 收到 FIN,回复 ACK | CLOSE_WAIT | 应用层会收到 EOF |
| 服务器 | FIN_WAIT_1 | 收到 ACK(对 FIN 的确认) | FIN_WAIT_2 | 等待客户端发送 FIN |
| 客户端 | CLOSE_WAIT | 应用层处理完数据后调用 close(),发送 FIN |
LAST_ACK | 主动发送 FIN |
| 服务器 | FIN_WAIT_2 | 收到客户端的 FIN,回复 ACK | TIME_WAIT | 进入 2MSL 等待 |
| 客户端 | LAST_ACK | 收到服务器对 FIN 的 ACK | CLOSED | 彻底关闭 |
| 服务器 | TIME_WAIT | 等待 2MSL 后 | CLOSED | 防止残留报文干扰 |
注意 :TIME_WAIT 只会出现在主动关闭方 。CLOSE_WAIT 和 LAST_ACK 出现在被动关闭方。
常见状态解释
| 状态 | 含义 | 出现位置 |
|---|---|---|
| LISTEN | 服务器监听,等待客户端连接 | 服务器 |
| SYN_SENT | 客户端已发送 SYN,等待 SYN+ACK | 客户端 |
| SYN_RCVD | 服务器收到 SYN,已回复 SYN+ACK,等待 ACK | 服务器 |
| ESTABLISHED | 连接已建立,可以传输数据 | 双方 |
| FIN_WAIT_1 | 主动关闭方已发送 FIN,等待 ACK | 主动方 |
| FIN_WAIT_2 | 主动关闭方已收到 FIN 的 ACK,等待对方 FIN | 主动方 |
| CLOSE_WAIT | 被动关闭方收到 FIN,等待应用层关闭 | 被动方 |
| LAST_ACK | 被动关闭方已发送 FIN,等待最终 ACK | 被动方 |
| TIME_WAIT | 主动关闭方收到 FIN 并回复 ACK,等待 2MSL | 主动方 |
| CLOSED | 连接完全关闭 | 双方 |
补充:
-
TIME_WAIT为什么要等 2MSL?-
保证主动关闭方发送的最后一个 ACK 能到达对方(若丢失,对方重发 FIN,自己还能响应)。
-
让本次连接的所有残留报文在网络中消失,避免干扰新连接。
-
-
CLOSE_WAIT容易出问题- 如果被动关闭方的应用层没有及时调用
close(),连接会一直卡在CLOSE_WAIT,导致文件描述符泄漏。
- 如果被动关闭方的应用层没有及时调用

详细解释:
一、前期准备(服务器端先启动)
1. 服务器端应用层
-
listenfd = socket():创建一个 监听 socket ,返回文件描述符listenfd。 -
bind(listenfd, 服务器地址端口):将listenfd绑定到服务器的 IP 和端口(例如0.0.0.0:8080)。 -
listen(listenfd, 连接队列长度):将listenfd变为被动监听状态,内核为该 socket 维护一个已完成连接队列(backlog)。
2. 服务器端 TCP 层状态
- 执行
listen()后,服务器 TCP 状态从CLOSED→LISTEN,等待客户端连接。
此时服务器阻塞在 accept() 调用上,等待客户端连接到来。
二、TCP 三次握手(建立连接)
1. 客户端应用层
-
fd = socket():创建主动 socket。 -
connect(fd, 服务器地址端口):发起连接请求,阻塞等待服务器应答。
2. 客户端 TCP 层状态
- 调用
connect()后,客户端状态:CLOSED→SYN_SENT(发送 SYN 报文)。
3. 服务器端 TCP 层
-
收到 SYN 后,状态:
LISTEN→SYN_RCVD,并回复 SYN+ACK。 -
客户端收到 SYN+ACK 后,状态:
SYN_SENT→ESTABLISHED,并回复 ACK。 -
服务器收到 ACK 后,状态:
SYN_RCVD→ESTABLISHED。
4. 应用层返回
-
客户端
connect()返回,表示连接建立成功。 -
服务器端阻塞的
accept()返回,生成一个新的已连接 socketconnfd,用于与该客户端通信。
注:
accept()返回后,服务器 TCP 层已经处于ESTABLISHED状态。
三、数据传输(可循环多次)
服务器端应用层:
-
read(connfd, buf, size):阻塞等待客户端请求数据。 -
收到数据后
read返回,处理请求 -
write(connfd, buf, size):向客户端发送应答数据。 -
继续循环
read→ 处理 →write,形成多次请求-应答(图中所示"循环多次")。
客户端应用层:
- 图中未画客户端的数据发送,但通常客户端也会
write发送请求,read接收应答。
TCP 层状态:
-
整个数据传输期间,双方 TCP 状态保持在
ESTABLISHED。 -
每次
write产生数据报文,收到ACK确认;每次read获取对方发来的数据。
图中客户端 TCP 层状态一栏写着
DATA和ACK,这并非正式状态,而是表示数据发送和确认阶段。
四、TCP 四次挥手(关闭连接)
以服务器端主动关闭为例(服务器调用 close(connfd))。
1. 服务器端应用层调用 close(connfd)
- TCP 层发送 FIN 报文,主动关闭。
2. 服务器端 TCP 状态转移
-
ESTABLISHED→FIN_WAIT_1(发送 FIN 后)。 -
收到客户端对 FIN 的 ACK 后:
FIN_WAIT_1→FIN_WAIT_2。 -
收到客户端的 FIN 后:发送 ACK,
FIN_WAIT_2→TIME_WAIT(等待 2MSL 后关闭)。 -
TIME_WAIT→CLOSED。
3. 客户端 TCP 状态转移(被动关闭)
-
收到 FIN 后:发送 ACK,
ESTABLISHED→CLOSE_WAIT(图中未显示,但标准是CLOSE_WAIT)。 -
客户端应用层调用
close()后:发送 FIN,状态CLOSE_WAIT→LAST_ACK。 -
收到服务器对 FIN 的 ACK 后:
LAST_ACK→CLOSED。
图中客户端 TCP 层状态只画到了
FIN_WAIT_2?实际上对客户端来说,被动关闭时没有FIN_WAIT_1/2,只有CLOSE_WAIT和LAST_ACK。图中可能简化了,或者画的是客户端主动关闭的情况。建议按标准理解。
五、"循环多次"的含义
-
服务器端 :在
accept()得到connfd后,在一个循环中反复read/write,处理同一条连接上的多次请求(例如 HTTP 持久连接)。 -
外层循环 :
accept()本身可以循环,接受多个客户端的连接,每个连接分配一个connfd,分别处理。
在服务器端标注了两个"循环多次":
-
内层:对同一个
connfd反复读请求 → 发应答。 -
外层:不断
accept()新连接。
六、关键总结
| 系统调用 | 作用 | 对应 TCP 状态变化 |
|---|---|---|
socket() |
创建套接字 | 不改变状态 |
bind() |
绑定地址端口 | 无状态变化 |
listen() |
变为监听套接字 | CLOSED → LISTEN |
accept() |
接受连接 | 返回已连接套接字,状态不变 |
connect() |
主动连接 | CLOSED → SYN_SENT → ESTABLISHED |
read()/write() |
读写数据 | 保持 ESTABLISHED |
close() |
关闭连接 | 触发四次挥手,状态变化如上 |
TCP 状态转换的汇总
• 较粗的虚线表示服务端的状态变化情况;
• 较粗的实线表示客户端的状态变化情况;
• CLOSED 是一个假想的起始点, 不是真实状态;

