当前位置: 首页 > news >正文

2.TCP深度解析:握手、挥手、状态机、流量与拥塞控制

文章目录

  • TCP深度解析:握手、挥手、状态机、流量与拥塞控制
    • 1. TCP三次握手(Three-Way Handshake)
      • 握手过程详解
      • 为什么需要三次握手?
      • 代码实现视角
    • 2. TCP四次挥手(Four-Way Handshake)
      • 挥手过程详解
      • 为什么需要四次挥手?
      • TIME_WAIT状态的重要性
    • 3. TCP状态转换图
      • 完整状态机
      • 状态持久化时间
    • 4. 流量控制(Flow Control) - 滑动窗口(Sliding Window)
      • 窗口机制原理
      • 零窗口和糊涂窗口综合征
    • 5. 拥塞控制(Congestion Control)
      • 拥塞控制状态机
      • 5.1 慢启动(Slow Start)
      • 5.2 拥塞避免(Congestion Avoidance)
      • 5.3 快速重传和快速恢复
      • 5.4 现代拥塞控制算法
        • BBR(Bottleneck Bandwidth and Round-trip time)
        • CUBIC
    • 6. 实际参数和调优
      • Linux TCP参数调优
    • 7. 常见问题
      • Q1: 为什么是三次握手而不是两次?
      • Q2: TIME_WAIT状态为什么要等待2MSL?
      • Q3: 滑动窗口和拥塞窗口的区别?
      • Q4: 慢启动为什么是指数增长?
      • Q5: 什么是快速重传?
    • 总结

TCP深度解析:握手、挥手、状态机、流量与拥塞控制

1. TCP三次握手(Three-Way Handshake)

握手过程详解

目的:同步连接双方的初始序列号(ISN),确认双方的收发能力正常。

过程

  1. SYN (Client -> Server):
    • 客户端发送一个SYN包(SYN=1),并选择一个初始序列号 seq = J
    • 客户端状态变为 SYN_SENT
  2. SYN-ACK (Server -> Client):
    • 服务器收到SYN包后,分配资源,并发送SYN-ACK包(SYN=1, ACK=1)。
    • 确认号 ack = J + 1,并选择自己的初始序列号 seq = K
    • 服务器状态变为 SYN_RCVD
  3. ACK (Client -> Server):
    • 客户端收到SYN-ACK后,分配资源,发送ACK包(ACK=1)。
    • 序列号 seq = J + 1,确认号 ack = K + 1
    • 客户端状态变为 ESTABLISHED
    • 服务器收到ACK后,状态也变为 ESTABLISHED。连接建立成功。

为什么需要三次握手?

核心原因:防止已失效的连接请求报文突然到达,导致服务器资源被浪费。

  • 场景:一个SYN报文滞留在网络中长期无效,客户端超时重传并成功建立连接。之后,无效的SYN报文终于到达服务器。
  • 两次握手:服务器会认为这是一个新的连接请求,直接回复SYN-ACK并进入连接状态,导致服务器资源空等。
  • 三次握手:客户端收到这个陈旧的SYN-ACK后,会发现自己并未请求连接,会发送RST包重置,服务器不会进入连接状态。

代码实现视角

// 客户端视角
void client_connect() {// 发送SYNsend_packet(SYN_FLAG, local_seq++);state = SYN_SENT;// 等待SYN-ACKwhile (state != ESTABLISHED) {packet = receive_packet();if (packet.has(SYN|ACK) && packet.ack == local_seq) {send_packet(ACK_FLAG, local_seq++, packet.seq + 1);state = ESTABLISHED;}}
}// 服务器视角
void server_listen() {while (true) {packet = receive_packet();if (packet.has(SYN)) {send_packet(SYN|ACK, local_seq++, packet.seq + 1);state = SYN_RCVD;// 等待ACKpacket = receive_packet();if (packet.has(ACK) && packet.ack == local_seq) {state = ESTABLISHED;}}}
}

2. TCP四次挥手(Four-Way Handshake)

挥手过程详解

目的:双方都同意关闭全双工连接。

过程

  1. FIN (主动关闭方 -> 被动关闭方):
    • 主动方发送FIN包(FIN=1),序列号为 seq = U
    • 主动方状态由 ESTABLISHED 变为 FIN_WAIT_1
  2. ACK (被动关闭方 -> 主动关闭方):
    • 被动方收到FIN后,发送ACK包(ACK=1),确认号 ack = U + 1
    • 被动方状态变为 CLOSE_WAIT
    • 主动方收到ACK后,状态变为 FIN_WAIT_2
    • 此时,从主动方到被动方的连接已关闭。但反向连接仍然可用。
  3. FIN (被动关闭方 -> 主动关闭方):
    • 被动方处理完所有数据后,发送自己的FIN包(FIN=1),序列号 seq = V
    • 被动方状态变为 LAST_ACK
  4. ACK (主动关闭方 -> 被动关闭方):
    • 主动方收到FIN后,发送ACK包(ACK=1),确认号 ack = V + 1
    • 主动方状态变为 TIME_WAIT
    • 被动方收到ACK后,状态变为 CLOSED
    • 主动方在 TIME_WAIT 状态等待 2MSL(两倍最大报文段生存时间)后,状态变为 CLOSED

为什么需要四次挥手?

// TCP是全双工协议,每个方向都需要单独关闭
void tcp_teardown_reason() {// 第一次挥手:客户端说"我没有数据要发了"// 第二次挥手:服务器说"我知道你要关了"// 第三次挥手:服务器说"我也没有数据要发了"  // 第四次挥手:客户端说"我知道你要关了"
}

TIME_WAIT状态的重要性

// 等待2MSL(Maximum Segment Lifetime)的原因:
void time_wait_importance() {// 1. 确保最后一个ACK能够到达对方//    - 如果ACK丢失,对方会重传FIN//    - 客户端在TIME_WAIT状态能够处理重传的FIN// 2. 让旧连接的重复报文在网络中消失//    - 避免影响新的连接(相同四元组)(源IP、源端口、目的IP、目的端口)的连接错误接收。// MSL通常为30秒到2分钟,2MSL就是1到4分钟
}

3. TCP状态转换图

完整状态机

必须理解的核心状态:

  • LISTEN:服务器等待SYN的状态。
  • SYN_SENT:客户端已发送SYN,等待SYN-ACK。
  • SYN_RCVD:服务器已发送SYN-ACK,等待ACK。
  • ESTABLISHED:连接已建立,可数据传输。
  • FIN_WAIT_1:主动关闭方已发送FIN。
  • FIN_WAIT_2:主动关闭方已收到对FIN的ACK。
  • CLOSE_WAIT:被动关闭方收到FIN,并已回复ACK。
  • LAST_ACK:被动关闭方已发送自己的FIN。
  • TIME_WAIT:主动关闭方发送完最后一个ACK,等待2MSL。
  • CLOSED:连接完全关闭。

记忆技巧:理解握手和挥手的过程,状态转换自然就记住了。

重要的状态转换:客户端状态流:
CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED服务器状态流:  
CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED异常情况:
- 同时打开:SYN_SENT → SYN_RCVD → ESTABLISHED
- 同时关闭:FIN_WAIT_1 → CLOSING → TIME_WAIT → CLOSED

状态持久化时间

// 各种状态的超时时间
struct tcp_timeouts {time_t syn_sent_timeout = 75;     // SYN_SENT超时(秒)time_t syn_rcvd_timeout = 75;     // SYN_RCVD超时(秒)time_t fin_wait_2_timeout = 60;   // FIN_WAIT_2超时(秒)time_t time_wait_timeout = 60;    // TIME_WAIT超时(2*MSL)time_t close_wait_timeout = 3600; // CLOSE_WAIT超时(应用控制)
};

4. 流量控制(Flow Control) - 滑动窗口(Sliding Window)

窗口机制原理

目的:解决发送方接收方速率不匹配的问题,防止发送过快导致接收方缓冲区溢出。

机制滑动窗口协议

  • 接收方在每次发送ACK时,都会通过 TCP首部中的“窗口大小”字段 告知发送方自己当前还能接收多少数据(接收窗口,rwnd)。
  • 发送方的发送窗口大小不能超过接收方通告的窗口大小。
  • 这个窗口是动态“滑动”的:接收方处理完数据,窗口右移,通告新的窗口大小;发送方收到确认,发送窗口也相应右移。

核心:接收方通过控制rwnd掌控发送方的发送速率。

零窗口和糊涂窗口综合征

// 零窗口处理
void handle_zero_window() {// 当接收方窗口为0时,发送方停止发送// 定期发送零窗口探测报文send_probe_packet(1); // 只发送1字节探测
}// 避免糊涂窗口综合征(Silly Window Syndrome)
void avoid_silly_window() {// 接收方策略:等待窗口达到一定大小再通告if (free_buffer < MIN_WINDOW_SIZE) {advertise_window(0); // 通告零窗口} else {advertise_window(free_buffer);}// 发送方策略:积累足够数据再发送if (data_to_send < MSS && !urgent) {wait_for_more_data();}
}

5. 拥塞控制(Congestion Control)

拥塞控制状态机

慢启动 → 拥塞避免 → 快速恢复

5.1 慢启动(Slow Start)

  • 目的:初始阶段快速探测网络容量。
  • 行为:连接刚建立时,cwnd = 1 MSS(一个最大报文段长度)。每收到一个ACK,cwnd加倍(指数增长)。
  • 结束条件cwnd超过慢启动阈值(ssthresh 时,进入拥塞避免阶段;或者遇到拥塞(超时)。
// 慢启动算法
void slow_start(struct tcp_connection* conn) {// 初始拥塞窗口(initcwnd):通常为2-10个MSSconn->cwnd = initial_cwnd;conn->ssthresh = 65535; // 初始慢启动阈值// 每个ACK到达时:cwnd = cwnd + 1*MSS// 效果:每个RTT时间窗口翻倍(指数增长)while (conn->cwnd < conn->ssthresh) {// 发送cwnd大小的数据send_data(conn->cwnd);// 等待ACK,每收到一个ACK:conn->cwnd += MSS;}// 进入拥塞避免阶段conn->state = CONGESTION_AVOIDANCE;
}

5.2 拥塞避免(Congestion Avoidance)

  • 目的:避免很快再次出现拥塞。
  • 行为:当cwnd >= ssthresh时,每收到一个ACK,cwnd增加 1/cwnd(线性增长,每RTT时间cwnd增加1个MSS)。
// 拥塞避免算法
void congestion_avoidance(struct tcp_connection* conn) {// 每个RTT时间:cwnd = cwnd + 1*MSS// 效果:线性增长// 每个ACK到达时:cwnd = cwnd + MSS*(MSS/cwnd)conn->cwnd += (MSS * MSS) / conn->cwnd;// 检测拥塞:超时或重复ACKif (packet_loss_detected()) {conn->ssthresh = max(2, conn->cwnd / 2);conn->cwnd = 1;conn->state = SLOW_START;}
}

5.3 快速重传和快速恢复

快速重传:

  • 目的:在定时器超时之前尽早重传丢失的报文。
  • 行为:如果发送方连续收到3个重复的ACK,就推断某个报文段丢失,立即重传该报文,而不必等待超时。

快速恢复:

  • 行为:在快重传之后触发。
    1. ssthresh = cwnd / 2
    2. cwnd = ssthresh (有的实现是 cwnd = ssthresh + 3,因为收到3个重复ACK表明有3个数据包离开了网络)
    3. 然后直接进入拥塞避免阶段(线性增长)。
  • 好处:避免了超时后cwnd被重置为1,性能更好。
// 快速重传算法
void fast_retransmit(struct tcp_connection* conn) {// 收到3个重复ACK时,认为报文丢失if (dup_ack_count >= 3) {// 立即重传丢失的报文retransmit_lost_packet();// 进入快速恢复conn->ssthresh = max(2, conn->cwnd / 2);conn->cwnd = conn->ssthresh + 3 * MSS;conn->state = FAST_RECOVERY;}
}// 快速恢复算法
void fast_recovery(struct tcp_connection* conn) {// 每收到一个重复ACK:cwnd = cwnd + MSS// 当新数据的ACK到达时:cwnd = ssthreshif (new_ack_received) {conn->cwnd = conn->ssthresh;conn->state = CONGESTION_AVOIDANCE;}
}

5.4 现代拥塞控制算法

BBR(Bottleneck Bandwidth and Round-trip time)
// Google的BBR算法
void bbr_algorithm(struct tcp_connection* conn) {// 基于带宽和延迟的拥塞控制estimate_bandwidth_delay(); // 估计带宽和RTT// 动态调整发送速率conn->pacing_rate = estimated_bandwidth * gain;conn->cwnd = estimated_bandwidth * min_rtt * gain;
}
CUBIC
// Linux默认的CUBIC算法
void cubic_algorithm(struct tcp_connection* conn) {// 使用三次函数调整窗口大小// 更公平,更适合高速网络conn->cwnd = C * (t - K)^3 + W_max;
}

6. 实际参数和调优

Linux TCP参数调优

# 查看当前TCP参数
sysctl -a | grep tcp# 调整拥塞控制算法
sysctl -w net.ipv4.tcp_congestion_control=cubic# 调整初始拥塞窗口
sysctl -w net.ipv4.tcp_initcwnd=10# 调整接收窗口大小
sysctl -w net.core.rmem_max=16777216
sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216'# 调整TIME_WAIT超时(谨慎使用)
sysctl -w net.ipv4.fin_timeout=30

7. 常见问题

Q1: 为什么是三次握手而不是两次?

:第三次握手确认客户端的接收能力正常,防止已失效的连接请求报文突然传到服务器导致错误连接建立。

Q2: TIME_WAIT状态为什么要等待2MSL?

:1) 确保最后一个ACK能够到达对方;2) 让旧连接的重复报文在网络中消失,避免影响新连接。

Q3: 滑动窗口和拥塞窗口的区别?

:滑动窗口是接收方控制的流量控制机制,拥塞窗口是发送方控制的拥塞避免机制。实际发送窗口取两者最小值。

Q4: 慢启动为什么是指数增长?

:为了快速探测网络可用带宽,在连接初期快速增加发送速率,尽快达到网络容量。

Q5: 什么是快速重传?

:当收到3个重复ACK时,不等待超时就立即重传丢失的报文,提高重传效率。

  1. 必知必会

    • 能画图并详细讲解三次握手和四次挥手的每一个步骤、状态变化及原因。
    • 能清晰区分流量控制拥塞控制的目的(谁 vs 谁)。
    • 能说出慢启动、拥塞避免、快重传、快恢复的触发条件和窗口变化规则。
  2. 深度问题

    • SYN Flood攻击的原理是什么?如何防范?(耗尽服务器的半连接队列;可用SYN Cookie防御)
    • TIME_WAIT状态过多有什么问题?如何优化?(占用端口资源;可设置SO_REUSEADDR套接字选项)
    • 除了TCP的拥塞控制,还了解哪些其他算法?(如BBR)

总结

TCP的复杂机制确保了可靠的数据传输:

  • 三次握手:可靠建立连接
  • 四次挥手:优雅关闭连接
  • 状态机:管理连接生命周期
  • 滑动窗口:流量控制,避免淹没接收方
  • 拥塞控制:网络保护,避免拥塞崩溃
http://www.xdnf.cn/news/1488655.html

相关文章:

  • 火山 RTC 引擎15 拉流 推流 地址生成器 、合流转推 开关
  • Vulkan 学习(20)---- UniformBuffer 的使用
  • 【系统分析师】第7章-基础知识:软件工程(核心总结)
  • 计算机毕设选题:基于Python+Django的B站数据分析系统的设计与实现【源码+文档+调试】
  • 阿里云上启动enclave 并与宿主机通信
  • 韧性双核系统:个人与关系的共生进化框架
  • 2024理想算法岗笔试笔记
  • HTTP中Payload的含义解析
  • MySQL集群高可用架构——组复制 (MGR)
  • Set集合
  • matrix-breakout-2-morpheus靶机渗透
  • 【从零开始学习Redis】秒杀优化——阻塞队列、消息队列实现异步秒杀
  • 虚拟机之CentOS、网络设置的有趣问题
  • openpyxl和excel数据驱动
  • C++20格式化字符串:std::format的使用与实践
  • 大坝安全监测中的单北斗GNSS变形监测系统应用解析
  • 宋红康 JVM 笔记 Day14|垃圾回收概述
  • Android --- AOSP源码导入Android Studio
  • 使用 Doxygen 生成 C++ 与 Python 项目文档
  • 腾讯云TDSQL-C 与传统MySQL对比
  • tf_keras包
  • 【工具变量】地级市中小企业数字化转型月度DID数据集(2022.1-2025.7)
  • 设计模式:模板方法模式(Template Method Pattern)
  • 设计模式:状态模式(State Pattern)
  • 【数据分析】一种用于校正微生物组数据中批次效应的多变量框架
  • 人工智能学习:Transformer架构
  • 简单的说一说前端开发语言React
  • 学习字符串
  • NW506NW507美光固态闪存NW525NW539
  • AI时代的软件开发革命:吴恩达关于快速工程的深度思考