从底层原理到实际应用,完整理解传输层两大核心协议的工作机制
TCP/UDP 位于传输层(第4层),负责端到端的数据传输
对应传输层,向上为应用层,向下为网络层
16位端口(0-65535),标识不同应用进程
多个应用共用网络,端口区分不同连接
UDP(User Datagram Protocol)是真正意义上的简单传输协议。 它只是简单地将数据报从源发送到目的地,不做任何额外处理。
// UDP 头部各字段说明 struct udphdr { __be16 source; // 源端口 - 发送方端口(可为零) __be16 dest; // 目标端口 - 接收方端口(必填) __be16 len; // UDP头部+数据的总长度 __be16 check; // 校验和(可选,IPv6必须) };
无需连接建立,无握手延迟,首包即可传输数据
保留完整消息边界,一个send对应一个recv
每个数据报独立处理,无连接状态开销
数据包独立,丢失一个不影响其他
TCP(Transmission Control Protocol)是互联网的支柱协议。 它通过精心设计的机制,在不可靠的IP网络上实现了可靠传输。
每个字节都有唯一序号,接收方确认已收数据
发送后等待确认,超时自动重发
建立可靠连接的同步过程
优雅关闭连接的同步过程
流量控制:避免发送过快
避免网络过载的自我调节
TCP通过三次握手在不可靠的IP网络上建立可靠连接。 这是一个双方同步序列号的过程。
点击"开始动画"观看三次握手的完整过程
// 三次握手详细过程 ┌─────────────────────────────────────────────────────────────────────┐ │ 第一次握手:客户端 → 服务端 SYN │ │ TCP头:SYN=1, Seq=x │ │ 含义:请求建立连接,客户端选择一个初始序列号 x │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 第二次握手:服务端 → 客户端 SYN+ACK │ │ TCP头:SYN=1, ACK=1, Seq=y, Ack=x+1 │ │ 含义:同意连接,服务端选择自己的序列号 y,确认收到 x │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 第三次握手:客户端 → 服务端 ACK │ │ TCP头:ACK=1, Seq=x+1, Ack=y+1 │ │ 含义:确认收到,服务端可以开始发送数据 │ └─────────────────────────────────────────────────────────────────────┘ // 为什么是三次?不是两次或四次? // 核心原因:TCP是全双工协议,双方都需要同步自己的初始序列号 // - 两次握手:服务端无法确认客户端是否收到了自己的SYN // - 四次握手:多余了,第二次的SYN和ACK可以合并发送
每次连接都会生成随机的ISN,防止历史残留数据干扰新连接
攻击者发送大量SYN但不完成握手,耗尽服务端资源
防御SYN Flood:在第三次握手才分配资源
TCP连接关闭需要四次挥手,因为是全双工通信, 每个方向都需要单独关闭。
点击"开始动画"观看四次挥手的完整过程
// 四次挥手详细过程 ┌─────────────────────────────────────────────────────────────────────┐ │ 第一次挥手:客户端 → 服务端 FIN │ │ 含义:客户端发送FIN,请求关闭客户端→服务端的连接 │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 第二次挥手:服务端 → 客户端 ACK │ │ 含义:服务端确认收到FIN,但此时服务端可能还有数据要发送 │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 第三次挥手:服务端 → 客户端 FIN │ │ 含义:服务端数据发送完毕,发送FIN关闭服务→客户端的连接 │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 第四次挥手:客户端 → 服务端 ACK │ │ 含义:客户端确认收到FIN,等待2MSL后彻底关闭 │ └─────────────────────────────────────────────────────────────────────┘ // TIME_WAIT 状态 // - 等待2MSL(Maximum Segment Lifetime,通常为60秒) // - 目的1:确保最后的ACK能到达对端(如果丢失,对端会重发FIN) // - 目的2:让本连接的所有数据包在网络中消散,避免干扰新连接
一方发送FIN后仍可接收数据,直到对方也发送FIN
使用RST标志可立即关闭连接,但可能导致数据丢失
两端同时发送FIN,握手过程变为三次(特殊情况的四次合并)
滑动窗口机制让TCP实现流量控制, 确保发送方不会因为发送太快而导致接收方缓冲区溢出。
// 滑动窗口工作原理 // 接收方在每次ACK中告诉发送方:rwnd(接收窗口大小) 发送方维护三个指针: 1. SND.UNA - 第一个未确认的字节 2. SND.NXT - 下一个要发送的字节 3. SND.WND - 当前窗口大小 窗口范围: 窗口左沿 = SND.UNA 窗口右沿 = SND.UNA + SND.WND 窗口滑动条件: - 收到ACK时,左沿右移 - 右沿右移条件:接收方有足够缓冲空间 // Zero Window(零窗口)问题 // 当接收方缓冲区满时,rwnd=0,发送方停止发送 // 接收方应用读取数据后,会发送一个rwnd>0的ACK // 发送方收到后恢复发送,这个ACK称为 Window Update
每个TCP socket都有接收缓冲区,存放到达的数据
存储已发送但未确认的数据 + 已应用写入但未发送的数据
零窗口后,发送方定期发送1字节探针触发Window Update
拥塞控制是TCP最重要的特性之一,它让TCP能够感知网络状况, 自动调整发送速率,避免网络过载导致崩溃。
cwnd从1开始,每个ACK翻倍增长。指数增长,快速探测可用带宽。
cwnd达到ssthresh后,每个RTT只增加1个MSS。线性增长。
收到3个重复ACK时,不等超时立即重传丢失的包。
快速重传后,进入快速恢复,cwnd减半后进入拥塞避免。
// 拥塞控制核心算法 // 1. 慢启动 while (cwnd < ssthresh) { cwnd = cwnd * 2; // 每个RTT翻倍 } // 近似指数增长:1 → 2 → 4 → 8 → 16 → ... // 2. 拥塞避免 while (cwnd >= ssthresh) { cwnd = cwnd + 1; // 每个RTT增加1个MSS } // 线性增长:N → N+1 → N+2 → N+3 → ... // 3. 检测拥塞 if (超时) { // 严重拥塞,重置连接 ssthresh = cwnd / 2; cwnd = 1; } else if (3个重复ACK) { // 轻度拥塞,部分丢包 ssthresh = cwnd / 2; cwnd = ssthresh + 3; // 快速恢复 }
| 指标 | TCP | UDP |
|---|---|---|
| 头部大小 | 20-60 字节 | 8 字节 |
| 延迟 | 较高(需握手) | 最低(无握手) |
| 可靠性 | ✅ 100% | ❌ 无 |
| 有序性 | ✅ 保证 | ❌ 不保证 |
| 流量控制 | ✅ 滑动窗口 | ❌ 无 |
| 拥塞控制 | ✅ 四种算法 | ❌ 无 |
| 单播/多播 | 仅单播 | 支持多播 |