文章目录
-
- TCP协议基础与连接管理详解:从三次握手到四次挥手
- 一、TCP报文格式详解
-
- [1.1 TCP报文的整体结构](#1.1 TCP报文的整体结构)
- [1.2 TCP首部的各个字段](#1.2 TCP首部的各个字段)
-
- [1. 源端口号和目的端口号(各16位)](#1. 源端口号和目的端口号(各16位))
- [2. 序列号(32位)](#2. 序列号(32位))
- [3. 确认号(32位)](#3. 确认号(32位))
- [4. TCP首部长度(4位)](#4. TCP首部长度(4位))
- [5. 标志位(6位)](#5. 标志位(6位))
- [6. 窗口大小(16位)](#6. 窗口大小(16位))
- [7. 校验和(16位)](#7. 校验和(16位))
- [8. 紧急指针(16位)](#8. 紧急指针(16位))
- [9. 选项(可变长度)](#9. 选项(可变长度))
- 二、TCP的连接建立:三次握手
-
- [2.1 为什么需要三次握手](#2.1 为什么需要三次握手)
- [2.2 三次握手的详细过程](#2.2 三次握手的详细过程)
- [2.3 三次握手的图示](#2.3 三次握手的图示)
- [2.4 为什么序列号要+1](#2.4 为什么序列号要+1)
- 三、TCP的连接关闭:四次挥手
-
- [3.1 为什么需要四次挥手](#3.1 为什么需要四次挥手)
- [3.2 四次挥手的详细过程](#3.2 四次挥手的详细过程)
- [3.3 四次挥手的图示](#3.3 四次挥手的图示)
- 四、TCP状态转换详解
-
- [4.1 TCP的11种状态](#4.1 TCP的11种状态)
- [4.2 服务器端的状态转换](#4.2 服务器端的状态转换)
- [4.3 客户端的状态转换](#4.3 客户端的状态转换)
- 五、TIME_WAIT状态深度理解
-
- [5.1 TIME_WAIT是什么](#5.1 TIME_WAIT是什么)
- [5.2 为什么需要TIME_WAIT](#5.2 为什么需要TIME_WAIT)
- [5.3 TIME_WAIT导致的问题](#5.3 TIME_WAIT导致的问题)
- [5.4 解决TIME_WAIT问题](#5.4 解决TIME_WAIT问题)
- 六、CLOSE_WAIT状态深度理解
-
- [6.1 CLOSE_WAIT是什么](#6.1 CLOSE_WAIT是什么)
- [6.2 CLOSE_WAIT的正常流程](#6.2 CLOSE_WAIT的正常流程)
- [6.3 CLOSE_WAIT的问题](#6.3 CLOSE_WAIT的问题)
- [6.4 CLOSE_WAIT问题的影响](#6.4 CLOSE_WAIT问题的影响)
- [6.5 解决CLOSE_WAIT问题](#6.5 解决CLOSE_WAIT问题)
- [6.6 排查CLOSE_WAIT问题](#6.6 排查CLOSE_WAIT问题)
- 七、本篇总结
-
- [7.1 核心要点](#7.1 核心要点)
- [7.2 容易混淆的点](#7.2 容易混淆的点)
TCP协议基础与连接管理详解:从三次握手到四次挥手
💬 开篇:上一篇我们学习了UDP------简单、快速、但不可靠。现在我们来看TCP的另一个极端:复杂、可靠、但相对较慢。TCP为什么这么复杂?因为它要保证数据可靠地到达对方。这一篇会详细讲解TCP的报文格式、三次握手建立连接、四次挥手断开连接、TCP状态转换,以及两个容易出现问题的状态:TIME_WAIT和CLOSE_WAIT。理解了TCP的连接管理,你就理解了为什么TCP能被称为"可靠的"协议。
👍 点赞、收藏与分享:这篇会把TCP的连接管理讲透,包括每一步的原因、状态转换、常见问题。如果对你有帮助,请点赞收藏!
🚀 循序渐进:从TCP报文格式讲起,到三次握手,到四次挥手,到状态转换,到TIME_WAIT和CLOSE_WAIT的深度理解,一步步掌握TCP的连接管理机制。
一、TCP报文格式详解
1.1 TCP报文的整体结构
TCP报文 = TCP首部 + TCP数据
bash
┌──────────────────────────────────────────────────────────┐
│ TCP首部(20-60字节) │
├──────────────────────────────────────────────────────────┤
│ TCP数据(可变长度) │
└──────────────────────────────────────────────────────────┘
TCP首部的最小长度:20字节(没有选项时)
TCP首部的最大长度:60字节(有选项时)
1.2 TCP首部的各个字段

1. 源端口号和目的端口号(各16位)
bash
┌─────────────────────┬─────────────────────┐
│ 源端口号(16位) │ 目的端口号(16位) │
└─────────────────────┴─────────────────────┘
作用:标识通信的两端应用程序。
范围:0-65535
2. 序列号(32位)
bash
┌──────────────────────────────────────────┐
│ 序列号(Sequence Number) │
└──────────────────────────────────────────┘
作用:
- 标识TCP报文中数据的第一个字节的序号
- 用于排序和去重
- 初始值是随机的(为了安全)
例子:
bash
第一个报文:序列号=1000,数据100字节
第二个报文:序列号=1100,数据100字节
第三个报文:序列号=1200,数据100字节
3. 确认号(32位)
bash
┌──────────────────────────────────────────┐
│ 确认号(Acknowledgment Number) │
└──────────────────────────────────────────┘
作用:
- 告诉对方"我已经收到了你的数据,下一个我要接收的序列号是多少"
- 只有ACK标志位为1时才有效
例子:
bash
收到对方的报文(序列号1000-1099)
发送ACK,确认号=1100(表示"我已收到1000-1099,下一个要1100")
4. TCP首部长度(4位)
bash
┌────────┐
│ 长度 │
└────────┘
含义:TCP首部有多少个32位字(4字节)。
计算:
bash
TCP首部长度 = 字段值 × 4 字节
例如:字段值=5 → TCP首部长度=20字节(最小)
例如:字段值=15 → TCP首部长度=60字节(最大)
为什么是4位:
bash
4位最大值=15
15 × 4 = 60字节(TCP首部最大长度)
5. 标志位(6位)
bash
│ URG │ ACK │ PSH │ RST │ SYN │ FIN │
各标志位的含义:
| 标志 | 名称 | 含义 |
|---|---|---|
| SYN | 同步 | 请求建立连接 |
| ACK | 确认 | 确认号有效 |
| FIN | 结束 | 请求关闭连接 |
| RST | 重置 | 重新建立连接 |
| PSH | 推送 | 立即发送,不等缓冲区满 |
| URG | 紧急 | 紧急指针有效 |
最常用的三个:SYN、ACK、FIN
6. 窗口大小(16位)
bash
┌──────────────────────┐
│ 窗口大小(16位) │
└──────────────────────┘
作用:告诉对方"我的接收缓冲区还有多少空间"。
用途:流量控制(后面详细讲)。
范围:0-65535字节
7. 校验和(16位)
bash
┌──────────────────────┐
│ 校验和(16位) │
└──────────────────────┘
校验和(checksum):用于检测传输过程中是否发生比特错误(覆盖 TCP 首部、数据和伪首部)。"
如果校验和出错:
bash
TCP层丢弃该报文,不通知应用层。
8. 紧急指针(16位)
bash
┌──────────────────────┐
│ 紧急指针(16位) │
└──────────────────────┘
作用:当URG标志为1时,指示哪部分数据是紧急数据。
使用场景:很少使用。
9. 选项(可变长度)
bash
┌──────────────────────────────────────┐
│ 选项(0-40字节) │
└──────────────────────────────────────┘
常见选项:
- MSS(Maximum Segment Size):最大报文段长度
- 窗口扩大因子:扩大窗口大小
- 时间戳:用于RTT计算和防止序列号绕回
二、TCP的连接建立:三次握手
2.1 为什么需要三次握手
问题:为什么不是一次握手或两次握手?
答案:需要确认双方都能收发数据。
分析:
一次握手:
bash
客户端 → 服务器:SYN
问题:服务器不知道客户端能否接收数据
两次握手:
bash
客户端 → 服务器:SYN
服务器 → 客户端:SYN+ACK
问题:客户端知道服务器能收发,但服务器不知道客户端能接收
三次握手:
bash
客户端 → 服务器:SYN
服务器 → 客户端:SYN+ACK
客户端 → 服务器:ACK
结果:双方都确认对方能收发
2.2 三次握手的详细过程
第一次握手:客户端发送SYN
客户端状态:CLOSED → SYN_SENT
发送的报文:
bash
SYN标志=1
序列号=x(客户端初始序列号,随机)
窗口大小=客户端接收缓冲区大小
含义:
bash
"嘿,我想和你建立连接。我的初始序列号是x。"
第二次握手:服务器回复SYN+ACK
服务器状态:LISTEN → SYN_RCVD
发送的报文:
bash
SYN标志=1
ACK标志=1
序列号=y(服务器初始序列号,随机)
确认号=x+1(确认收到了客户端的序列号x)
窗口大小=服务器接收缓冲区大小
含义:
bash
"好的,我收到你的连接请求了。我的初始序列号是y。
下一个我要接收的是你的序列号x+1。"
第三次握手:客户端发送ACK
客户端状态:SYN_SENT → ESTABLISHED
发送的报文:
bash
ACK标志=1
序列号=x+1(继续使用自己的序列号)
确认号=y+1(确认收到了服务器的序列号y)
含义:
bash
"好的,我收到你的回复了。下一个我要接收的是你的序列号y+1。"
服务器状态:SYN_RCVD → ESTABLISHED
2.3 三次握手的图示

bash
客户端 服务器
| |
| 第一次握手:SYN(seq=x) |
|----------------------------->|
| |
| | 状态:LISTEN → SYN_RCVD
| |
| 第二次握手:SYN+ACK(seq=y, ack=x+1)
|<-----------------------------|
| |
| 状态:SYN_SENT → ESTABLISHED |
| |
| 第三次握手:ACK(seq=x+1, ack=y+1)
|----------------------------->|
| |
| | 状态:SYN_RCVD → ESTABLISHED
| |
| 连接建立,可以传输数据 |
|<---------------------------->|
2.4 为什么序列号要+1
问题:为什么确认号是x+1而不是x?
答案:确认号表示"下一个我要接收的序列号"。
例子:
bash
收到序列号为1000的报文(包含100字节数据)
这个报文的字节序号是1000-1099
下一个我要接收的字节序号是1100
所以确认号=1100
在握手中:
bash
收到SYN报文(序列号=x)
虽然SYN报文没有数据,但SYN本身占用一个序列号
所以下一个要接收的序列号是x+1
确认号=x+1
三、TCP的连接关闭:四次挥手
3.1 为什么需要四次挥手
问题:为什么不是三次或两次?
答案:TCP是全双工的,双方都可以发送数据。
分析:
两次挥手:
bash
客户端 → 服务器:FIN(我要关闭了)
服务器 → 客户端:FIN(我也要关闭了)
问题:服务器可能还有数据要发送给客户端
四次挥手:
bash
客户端 → 服务器:FIN(我要关闭了)
服务器 → 客户端:ACK(我收到了)
服务器 → 客户端:FIN(我也要关闭了)
客户端 → 服务器:ACK(我收到了)
结果:双方都确认对方已关闭
3.2 四次挥手的详细过程
第一次挥手:客户端发送FIN
客户端状态:ESTABLISHED → FIN_WAIT_1
发送的报文:
bash
FIN标志=1
序列号=x(继续使用自己的序列号)
含义:
bash
"我已经发送完所有数据了,现在要关闭连接。"
重要:客户端发送FIN后,不再发送数据,但仍然可以接收数据。
第二次挥手:服务器回复ACK
服务器状态:ESTABLISHED → CLOSE_WAIT
发送的报文:
bash
ACK标志=1
确认号=x+1(确认收到了客户端的FIN)
含义:
bash
"好的,我收到你的关闭请求了。"
重要:服务器进入CLOSE_WAIT状态,表示"我收到了关闭请求,但我还有数据要发送"。
第三次挥手:服务器发送FIN
服务器状态:CLOSE_WAIT → LAST_ACK
发送的报文:
bash
FIN标志=1
序列号=y(继续使用自己的序列号)
含义:
bash
"我已经发送完所有数据了,现在也要关闭连接。"
时机:服务器在CLOSE_WAIT状态下,处理完所有待发送的数据后,才发送FIN。
第四次挥手:客户端回复ACK
客户端状态:FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT
发送的报文:
bash
ACK标志=1
确认号=y+1(确认收到了服务器的FIN)
含义:
bash
"好的,我收到你的关闭请求了。"
重要:客户端进入TIME_WAIT状态,等待2MSL时间后才进入CLOSED状态。
服务器状态:LAST_ACK → CLOSED
3.3 四次挥手的图示

bash
客户端 服务器
| |
| 第一次挥手:FIN(seq=x) |
|----------------------------->|
| |
| 状态:ESTABLISHED → FIN_WAIT_1|
| | 状态:ESTABLISHED → CLOSE_WAIT
| |
| 第二次挥手:ACK(ack=x+1) |
|<-----------------------------|
| |
| 状态:FIN_WAIT_1 → FIN_WAIT_2 |
| | 处理剩余数据...
| |
| 第三次挥手:FIN(seq=y) |
|<-----------------------------|
| |
| 状态:FIN_WAIT_2 → TIME_WAIT | 状态:CLOSE_WAIT → LAST_ACK
| |
| 第四次挥手:ACK(ack=y+1) |
|----------------------------->|
| |
| 等待2MSL | 状态:LAST_ACK → CLOSED
| |
| 状态:TIME_WAIT → CLOSED |
四、TCP状态转换详解
4.1 TCP的11种状态
| 状态 | 含义 |
|---|---|
| CLOSED | 连接已关闭 |
| LISTEN | 监听状态,等待连接 |
| SYN_SENT | 已发送SYN,等待响应 |
| SYN_RCVD | 已收到SYN,已发送SYN+ACK |
| ESTABLISHED | 连接已建立,可以传输数据 |
| FIN_WAIT_1 | 已发送FIN,等待ACK |
| FIN_WAIT_2 | 已收到ACK,等待对方的FIN |
| CLOSE_WAIT | 已收到FIN,等待应用层关闭 |
| LAST_ACK | 已发送FIN,等待最后的ACK |
| TIME_WAIT | 已发送最后的ACK,等待2MSL |
| CLOSING | 同时收到FIN(罕见) |
4.2 服务器端的状态转换
bash
CLOSED
↓ (调用listen)
LISTEN
↓ (收到SYN)
SYN_RCVD
↓ (收到ACK)
ESTABLISHED
↓ (收到FIN)
CLOSE_WAIT
↓ (调用close)
LAST_ACK
↓ (收到ACK)
CLOSED
4.3 客户端的状态转换
bash
CLOSED
↓ (调用connect)
SYN_SENT
↓ (收到SYN+ACK)
ESTABLISHED
↓ (调用close)
FIN_WAIT_1
↓ (收到ACK)
FIN_WAIT_2
↓ (收到FIN)
TIME_WAIT
↓ (等待2MSL)
CLOSED
五、TIME_WAIT状态深度理解
5.1 TIME_WAIT是什么
TIME_WAIT:主动关闭连接的一方进入的状态。
持续时间:2MSL(Maximum Segment Lifetime)
- TIME_WAIT 持续 2MSL。RFC 1122 建议 MSL 取 2 分钟(因此 2MSL=4 分钟),不同实现可能更短。
- Linux 的
tcp_fin_timeout影响 FIN_WAIT_2,不同于 TIME_WAIT。
5.2 为什么需要TIME_WAIT
原因1:确保最后的ACK能到达
场景:
bash
客户端发送最后的ACK
这个ACK丢失了
服务器没收到,会重新发送FIN
TIME_WAIT的作用:
bash
客户端在TIME_WAIT状态下,仍然可以接收数据
如果收到重复的FIN,会重新发送ACK
图示:
bash
客户端 服务器
| |
| 第四次挥手:ACK(ack=y+1) |
|----------------------------->|
| |
| 进入TIME_WAIT | 等待ACK...
| |
| ACK丢失了! |
| |
| | 超时,重新发送FIN
| 第三次挥手(重复):FIN(seq=y)|
|<-----------------------------|
| |
| 收到重复的FIN,重新发送ACK |
|----------------------------->|
| |
| 继续等待2MSL | 收到ACK,关闭
| |
| 2MSL后关闭 |
原因2:防止旧连接的数据干扰新连接
场景:
bash
连接1:客户端192.168.1.100:54321 ↔ 服务器192.168.1.1:8080
连接1关闭,但有些数据包还在网络中漂浮
连接2:客户端192.168.1.100:54321 ↔ 服务器192.168.1.1:8080
(使用了相同的五元组)
旧连接的数据包到达,被新连接误认为是新数据
TIME_WAIT的作用:
bash
等待2MSL,确保所有旧数据包都消失
然后才允许使用相同的五元组建立新连接
5.3 TIME_WAIT导致的问题
问题:当服务端作为主动关闭方、且快速重启时,可能遇到 Address already in use;
场景:
bash
# 启动服务器
./server 8080
# 运行一段时间后,按Ctrl+C关闭
# 立刻重启
./server 8080
# 错误:Address already in use
原因:
bash
服务器主动关闭连接,进入TIME_WAIT状态
TIME_WAIT期间,端口8080仍然被占用
无法bind到同一个端口
查看TIME_WAIT连接:
bash
netstat -an | grep TIME_WAIT
5.4 解决TIME_WAIT问题
方法1:使用SO_REUSEADDR选项
cpp
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(sockfd, ...);
效果:允许bind到处于TIME_WAIT状态的端口。
原理:
bash
`SO_REUSEADDR` 允许在一定条件下重新绑定处于 TIME_WAIT 相关状态的本地地址/端口(常用于服务快速重启)。
不同系统/场景表现有差异,生产上以实际测试为准。
方法2:等待TIME_WAIT过期
方法3:修改TIME_WAIT的值
不建议试图通过 sysctl 直接修改 TIME_WAIT 时长:另外tcp_fin_timeout 影响的是FIN_WAIT_2,不等同 TIME_WAIT。真正想改 TIME_WAIT 时长通常涉及内核实现,不适合生产环境。
六、CLOSE_WAIT状态深度理解
6.1 CLOSE_WAIT是什么
CLOSE_WAIT:被动关闭连接的一方进入的状态。
含义:
bash
"我收到了对方的关闭请求,但我还有数据要发送。"
6.2 CLOSE_WAIT的正常流程
bash
1. 收到对方的FIN
2. 进入CLOSE_WAIT状态
3. 处理剩余的数据
4. 调用close()关闭连接
5. 发送FIN
6. 进入LAST_ACK状态
7. 收到对方的ACK
8. 进入CLOSED状态
6.3 CLOSE_WAIT的问题
问题:大量CLOSE_WAIT连接堆积。
原因:应用程序没有正确关闭socket。
例子(错误的代码):
cpp
// 服务器代码
for (;;) {
TcpSocket new_sock;
listen_sock.Accept(&new_sock, ...);
for (;;) {
std::string req;
if (!new_sock.Recv(&req)) {
// 客户端关闭了连接(收到FIN)
// 服务器进入CLOSE_WAIT状态
// 但没有调用new_sock.Close()
break; // ← 这里直接break了,没有关闭socket
}
// 处理请求...
}
// ← 缺少:new_sock.Close();
}
后果:
bash
1. 客户端调用close() → 发送FIN
2. 服务器收到FIN → 自动回复ACK → 进入CLOSE_WAIT
3. 服务器的应用层break,但没有调用close()
4. 服务器一直停留在CLOSE_WAIT状态
5. 连接无法完全关闭,资源无法释放
查看CLOSE_WAIT连接:
bash
netstat -an | grep CLOSE_WAIT
# 或
ss -tan | grep CLOSE_WAIT
输出示例(大量堆积):
bash
tcp 1 0 127.0.0.1:8080 127.0.0.1:54321 CLOSE_WAIT
tcp 1 0 127.0.0.1:8080 127.0.0.1:54322 CLOSE_WAIT
tcp 1 0 127.0.0.1:8080 127.0.0.1:54323 CLOSE_WAIT
tcp 1 0 127.0.0.1:8080 127.0.0.1:54324 CLOSE_WAIT
... (几百个甚至几千个)
6.4 CLOSE_WAIT问题的影响
资源泄露:
bash
每个CLOSE_WAIT连接都占用:
- 一个文件描述符
- 内核中的TCP连接结构
- 接收和发送缓冲区
文件描述符耗尽:
bash
Linux默认的文件描述符限制:1024(ulimit -n查看)
如果有1000个CLOSE_WAIT连接,就无法创建新连接了
性能下降:
bash
大量无用的连接占用系统资源
影响正常的连接处理
6.5 解决CLOSE_WAIT问题
唯一的方法:正确关闭socket
正确的代码:
cpp
// 服务器代码
for (;;) {
TcpSocket new_sock;
listen_sock.Accept(&new_sock, ...);
for (;;) {
std::string req;
if (!new_sock.Recv(&req)) {
// 客户端关闭了连接
printf("Client disconnected\n");
new_sock.Close(); // ← 关键:调用close()
break;
}
// 处理请求...
}
}
RAII封装(推荐):
cpp
class TcpSocket {
public:
~TcpSocket() {
Close(); // 析构时自动关闭
}
void Close() {
if (fd_ >= 0) {
close(fd_);
fd_ = -1;
}
}
private:
int fd_;
};
使用RAII后:
cpp
{
TcpSocket new_sock;
listen_sock.Accept(&new_sock, ...);
// ... 处理请求 ...
} // ← new_sock析构时自动调用Close(),不会忘记关闭
6.6 排查CLOSE_WAIT问题
步骤1:确认是否有大量CLOSE_WAIT
bash
netstat -an | grep CLOSE_WAIT | wc -l
步骤2:找到问题进程
bash
netstat -anp | grep CLOSE_WAIT
# 输出会显示进程PID和名称
步骤3:检查代码
bash
搜索所有Accept()的地方
确认每个Accept后都有对应的Close()
步骤4:使用工具检测
bash
# 使用lsof查看进程打开的文件描述符
lsof -p <PID> | grep CLOSE_WAIT
七、本篇总结
7.1 核心要点
TCP报文格式:
- 首部20-60字节,包含源端口、目的端口、序列号、确认号等
- 六个标志位:SYN、ACK、FIN、RST、PSH、URG
- 序列号用于排序和去重
- 确认号表示"下一个要接收的序列号"
三次握手:
- 第一次:客户端发送SYN,进入SYN_SENT
- 第二次:服务器回复SYN+ACK,进入SYN_RCVD
- 第三次:客户端发送ACK,双方进入ESTABLISHED
- 目的:确认双方都能收发数据
四次挥手:
- 第一次:客户端发送FIN,进入FIN_WAIT_1
- 第二次:服务器回复ACK,进入CLOSE_WAIT
- 第三次:服务器发送FIN,进入LAST_ACK
- 第四次:客户端回复ACK,进入TIME_WAIT
- 目的:优雅地关闭双向连接
TIME_WAIT:
- 主动关闭方进入的状态
- 持续2MSL(通常120秒)
- 目的1:确保最后的ACK能到达
- 目的2:防止旧连接的数据干扰新连接
- 解决方法:使用SO_REUSEADDR
CLOSE_WAIT:
- 被动关闭方进入的状态
- 表示"我收到FIN了,但还没close()"
- 问题:应用层忘记调用close(),导致大量堆积
- 解决方法:确保每个accept后都有close()
7.2 容易混淆的点
-
序列号和确认号:
- 序列号:我发送的数据的第一个字节的序号
- 确认号:下一个我要接收的序列号
-
SYN和FIN占用序列号:
- 虽然它们不携带数据,但各占用1个序列号
- 所以确认号要+1
-
TIME_WAIT vs CLOSE_WAIT:
- TIME_WAIT:主动关闭方,正常状态
- CLOSE_WAIT:被动关闭方,如果大量堆积说明有bug
-
三次握手vs四次挥手:
- 三次:因为服务器可以把SYN和ACK合并发送
- 四次:因为服务器可能还有数据要发送,不能合并
-
半关闭:
- 一方关闭了发送方向,但接收方向仍然开放
- 四次挥手的第二次和第三次之间就是半关闭状态
💬 总结:TCP通过复杂的连接管理机制,实现了可靠的数据传输。三次握手确保双方都能收发数据,四次挥手优雅地关闭连接。TIME_WAIT是主动关闭方的正常状态,而CLOSE_WAIT大量堆积则是应用层的bug。理解了这些状态转换,你就理解了TCP为什么是"可靠的"。下一篇我们会讲TCP的可靠性机制(确认应答、超时重传、滑动窗口、流量控制、拥塞控制),看看TCP如何通过这些机制保证数据的可靠传输。
👍 点赞、收藏与分享:如果这篇帮你理解了TCP的连接管理和状态转换,请点赞收藏!网络编程,从理解TCP开始!