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

文章目录

  • TCP深度解析:握手、挥手、状态机、流量与拥塞控制
    • [1. TCP三次握手(Three-Way Handshake)](#1. TCP三次握手(Three-Way Handshake))
    • [2. TCP四次挥手(Four-Way Handshake)](#2. TCP四次挥手(Four-Way Handshake))
    • [3. TCP状态转换图](#3. TCP状态转换图)
    • [4. 流量控制(Flow Control) - 滑动窗口(Sliding Window)](#4. 流量控制(Flow Control) - 滑动窗口(Sliding Window))
    • [5. 拥塞控制(Congestion Control)](#5. 拥塞控制(Congestion Control))
      • 拥塞控制状态机
      • [5.1 慢启动(Slow Start)](#5.1 慢启动(Slow Start))
      • [5.2 拥塞避免(Congestion Avoidance)](#5.2 拥塞避免(Congestion Avoidance))
      • [5.3 快速重传和快速恢复](#5.3 快速重传和快速恢复)
      • [5.4 现代拥塞控制算法](#5.4 现代拥塞控制算法)
        • [BBR(Bottleneck Bandwidth and Round-trip time)](#BBR(Bottleneck Bandwidth and Round-trip time))
        • CUBIC
    • [6. 实际参数和调优](#6. 实际参数和调优)
      • [Linux TCP参数调优](#Linux TCP参数调优)
    • [7. 常见问题](#7. 常见问题)
      • [Q1: 为什么是三次握手而不是两次?](#Q1: 为什么是三次握手而不是两次?)
      • [Q2: TIME_WAIT状态为什么要等待2MSL?](#Q2: TIME_WAIT状态为什么要等待2MSL?)
      • [Q3: 滑动窗口和拥塞窗口的区别?](#Q3: 滑动窗口和拥塞窗口的区别?)
      • [Q4: 慢启动为什么是指数增长?](#Q4: 慢启动为什么是指数增长?)
      • [Q5: 什么是快速重传?](#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包重置,服务器不会进入连接状态。

代码实现视角

c 复制代码
// 客户端视角
void client_connect() {
    // 发送SYN
    send_packet(SYN_FLAG, local_seq++);
    state = SYN_SENT;
    
    // 等待SYN-ACK
    while (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;
            
            // 等待ACK
            packet = 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

为什么需要四次挥手?

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

TIME_WAIT状态的重要性

c 复制代码
// 等待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

状态持久化时间

c 复制代码
// 各种状态的超时时间
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掌控发送方的发送速率。

零窗口和糊涂窗口综合征

c 复制代码
// 零窗口处理
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 时,进入拥塞避免阶段;或者遇到拥塞(超时)。
c 复制代码
// 慢启动算法
void slow_start(struct tcp_connection* conn) {
    // 初始拥塞窗口(initcwnd):通常为2-10个MSS
    conn->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)。
c 复制代码
// 拥塞避免算法
void congestion_avoidance(struct tcp_connection* conn) {
    // 每个RTT时间:cwnd = cwnd + 1*MSS
    // 效果:线性增长
    
    // 每个ACK到达时:cwnd = cwnd + MSS*(MSS/cwnd)
    conn->cwnd += (MSS * MSS) / conn->cwnd;
    
    // 检测拥塞:超时或重复ACK
    if (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,性能更好。
c 复制代码
// 快速重传算法
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 = ssthresh
    if (new_ack_received) {
        conn->cwnd = conn->ssthresh;
        conn->state = CONGESTION_AVOIDANCE;
    }
}

5.4 现代拥塞控制算法

BBR(Bottleneck Bandwidth and Round-trip time)
c 复制代码
// 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
c 复制代码
// Linux默认的CUBIC算法
void cubic_algorithm(struct tcp_connection* conn) {
    // 使用三次函数调整窗口大小
    // 更公平,更适合高速网络
    conn->cwnd = C * (t - K)^3 + W_max;
}

6. 实际参数和调优

Linux TCP参数调优

bash 复制代码
# 查看当前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的复杂机制确保了可靠的数据传输:

  • 三次握手:可靠建立连接
  • 四次挥手:优雅关闭连接
  • 状态机:管理连接生命周期
  • 滑动窗口:流量控制,避免淹没接收方
  • 拥塞控制:网络保护,避免拥塞崩溃
相关推荐
AlfredZhao3 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334669 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪10 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
网络研究院1 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
treesforest1 天前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言