TCP从零单排

0 阅读7分钟

眉清目秀原来是平铺直叙 image.png

image.png

TCP 协议:从握手到数据传输

序章:TCP 是什么?—— 网络世界的"可靠快递"

tcp是可靠的传输协议,结合日常生活把这些概念套进去,就想象自己如果碰到这样的问题会如何处理,这样更易理解,不用死记硬背。 想象你要寄一份重要合同给朋友:

  • UDP 像普通明信片:便宜、快,但可能丢失、乱序、重复
  • TCP 像顺丰快递:贵一点、慢一点,但保证送达、按顺序、不重复

TCP(Transmission Control Protocol,传输控制协议) 是互联网最核心的协议之一,位于传输层,提供面向连接、可靠、基于字节流的通信服务。

graph TD
    A[应用层<br/>HTTP/FTP/DNS] --> B[传输层<br/>TCP/UDP]
    B --> C[网络层<br/>IP]
    C --> D[数据链路层<br/>以太网]
    D --> E[物理层<br/>光纤/电缆]
    
    style B fill:#90EE90

第一章:TCP 特点 —— 五大核心特性

mindmap
  root((TCP<br/>五大特点))
    面向连接
      通信前先建立连接
      像打电话要拨号
    可靠传输
      不丢包
      不错乱
      不重复
    全双工通信
      双方可同时发送和接收
      像双向车道
    字节流服务
      不保留消息边界
      像水流连续不断
    点对点
      一对一通信
      不支持广播

1.1 面向连接 —— 先打电话,再说话

sequenceDiagram
    participant A as 客户端
    participant B as 服务端
    
    Note over A,B: UDP:直接喊话<br/>"喂!有人吗?"
    
    A->>B: TCP:先建立连接
    B->>A: 三次握手完成
    Note over A,B: 现在可以可靠通信了
    A->>B: 发送数据
    B->>A: 确认收到

1.2 可靠传输 —— 签收确认机制

TCP 通过四种机制保证可靠性:

  • 校验和:检查数据是否损坏
  • 确认应答 ACK:收到后回复"收到了"
  • 超时重传:没收到确认就重发
  • 序号机制:保证数据按序到达

1.3 全双工通信 —— 双向车道

graph LR
    A[客户端] -->|发送数据| B[服务端]
    B -->|同时发送| A
    
    subgraph "全双工"
        C[发送通道] 
        D[接收通道]
    end
    
    style C fill:#90EE90
    style D fill:#90EE90

1.4 字节流服务 —— 像水流一样

// TCP 不保留消息边界
// 发送方分两次发送:"Hello" 和 "World"
conn.Write([]byte("Hello"))
conn.Write([]byte("World"))

// 接收方可能收到:
// 情况1: "HelloWorld"(粘包)
// 情况2: "Hel" + "loWorld"(拆包)
// 情况3: "Hello" + "World"(刚好)

// 解决方案:定义消息边界
// 方式1:固定长度
// 方式2:特殊分隔符(如 \n)
// 方式3:长度前缀(推荐)

1.5 点对点 —— 一对一专属

TCP 连接是一对一的,每个连接由四元组唯一标识:

(源IP, 源端口, 目的IP, 目的端口)

第二章:三次握手 —— 建立连接的"三步曲"

2.1 为什么需要三次握手?

故事类比:两个人在嘈杂的酒吧里确认对方能听到自己说话。

sequenceDiagram
    participant Client as 客户端(A)
    participant Server as 服务端(B)
    
    Note over Client,Server: 第一次握手:A 确认自己能发,B 确认自己能收
    
    Client->>Server: SYN=1, seq=x<br/>(我能听到你吗?我的序号是x)
    
    Note over Client,Server: 第二次握手:B 确认自己能发能收,A 确认B能收发
    
    Server->>Client: SYN=1, ACK=1, seq=y, ack=x+1<br/>(我能听到!我的序号是y,期待你的x+1)
    
    Note over Client,Server: 第三次握手:A 确认自己能收,B 确认A能发
    
    Client->>Server: ACK=1, seq=x+1, ack=y+1<br/>(我也能听到!给你x+1,期待你的y+1)
    
    Note over Client,Server: 连接建立!双方确认:我能发,你能收;你能发,我能收

2.2 详细解析每个字段

握手次数发送方关键标志含义
第一次客户端SYN=1, seq=x请求同步,携带初始序号 x
第二次服务端SYN=1, ACK=1, seq=y, ack=x+1同意同步,确认收到,携带自己的序号 y
第三次客户端ACK=1, seq=x+1, ack=y+1确认收到,序号+1,准备传输数据

2.3 为什么不能两次握手?

image.png

Go 代码示例:TCP 连接建立

package main

import (
    "fmt"
    "net"
)

func main() {
    // 服务端
    go func() {
        listener, err := net.Listen("tcp", ":8080")
        if err != nil {
            panic(err)
        }
        defer listener.Close()
        
        fmt.Println("服务端:等待连接...")
        
        conn, err := listener.Accept()
        if err != nil {
            panic(err)
        }
        
        fmt.Printf("服务端:连接建立!远端地址 %s\n", conn.RemoteAddr())
        
        // 读取数据
        buf := make([]byte, 1024)
        n, _ := conn.Read(buf)
        fmt.Printf("服务端收到:%s\n", string(buf[:n]))
        
        conn.Write([]byte("Hello Client!"))
        conn.Close()
    }()
    
    // 客户端
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    
    fmt.Printf("客户端:连接建立!本地 %s -> 远端 %s\n", 
        conn.LocalAddr(), conn.RemoteAddr())
    
    conn.Write([]byte("Hello Server!"))
    
    buf := make([]byte, 1024)
    n, _ := conn.Read(buf)
    fmt.Printf("客户端收到:%s\n", string(buf[:n]))
}

输出:

服务端:等待连接...
客户端:连接建立!本地 127.0.0.1:54321 -> 远端 127.0.0.1:8080
服务端:连接建立!远端地址 127.0.0.1:54321
服务端收到:Hello Server!
客户端收到:Hello Server!

第三章:四次挥手 —— 优雅分手的"四步曲"

3.1 为什么需要四次挥手?

故事类比:情侣分手,双方都要确认"我说完了"和"我听完了"。

sequenceDiagram
    participant Client as 客户端(A)
    participant Server as 服务端(B)
    
    Note over Client,Server: A 想关闭连接,但 B 可能还有数据要发
    
    Client->>Server: FIN=1, seq=u<br/>(A:我说完了,要关闭发送)
    
    Server->>Client: ACK=1, seq=v, ack=u+1<br/>(B:知道了,但我还没说完)
    
    Note over Client,Server: B 继续发送剩余数据...
    
    Server->>Client: FIN=1, seq=w, ack=u+1<br/>(B:我也说完了,要关闭发送)
    
    Client->>Server: ACK=1, seq=u+1, ack=w+1<br/>(A:知道了,等待2MSL后关闭)
    
    Note over Client,Server: 连接完全关闭

3.2 详细解析

挥手次数发送方关键标志状态变化
第一次客户端FIN=1, seq=u客户端:FIN_WAIT_1
第二次服务端ACK=1, seq=v, ack=u+1服务端:CLOSE_WAIT;客户端:FIN_WAIT_2
第三次服务端FIN=1, seq=w, ack=u+1服务端:LAST_ACK
第四次客户端ACK=1, seq=u+1, ack=w+1客户端:TIME_WAIT(等待2MSL后关闭)

3.3 为什么客户端最后要等待 2MSL?

image.png

TCP 状态转换全景图:

stateDiagram-v2
    [*] --> CLOSED
    
    CLOSED --> SYN_SENT : 主动打开<br/>发送SYN
    CLOSED --> LISTEN : 被动打开
    
    LISTEN --> SYN_RCVD : 收到SYN<br/>发送SYN+ACK
    SYN_SENT --> ESTABLISHED : 收到SYN+ACK<br/>发送ACK
    SYN_RCVD --> ESTABLISHED : 收到ACK
    
    ESTABLISHED --> FIN_WAIT_1 : 主动关闭<br/>发送FIN
    ESTABLISHED --> CLOSE_WAIT : 收到FIN<br/>发送ACK
    
    FIN_WAIT_1 --> FIN_WAIT_2 : 收到ACK
    FIN_WAIT_1 --> TIME_WAIT : 收到FIN+ACK<br/>发送ACK(同时关闭)
    FIN_WAIT_2 --> TIME_WAIT : 收到FIN<br/>发送ACK
    
    CLOSE_WAIT --> LAST_ACK : 发送FIN
    LAST_ACK --> CLOSED : 收到ACK
    
    TIME_WAIT --> CLOSED : 等待2MSL
    
    
    
    note right of TIME_WAIT
        为什么TIME_WAIT?
        1. 确保ACK到达
        2. 防止旧报文干扰
    end note

第四章:可靠传输 —— 四大保障机制

graph TD
    A[TCP可靠传输] --> B[校验和]
    A --> C[确认应答ACK]
    A --> D[超时重传]
    A --> E[序号机制]
    A --> F[去重]
    
    style A fill:#E1F5FE

4.1 校验和 —— 检查数据是否损坏

graph LR
    A[发送方] -->|数据 + 校验和| B[网络传输]
    B --> C[接收方]
    C --> D{校验和匹配?}
    D -->|是| E[数据正常<br/>回复ACK]
    D -->|否| F[数据损坏<br/>丢弃不回复<br/>触发重传]
    
    style E fill:#90EE90
    style F fill:#FFB6C1

计算方式:将数据分成16位字,求和,取反码。

// 简化的校验和计算(示意)
func checksum(data []byte) uint16 {
    var sum uint32
    
    // 按16位字求和
    for i := 0; i < len(data)-1; i += 2 {
        sum += uint32(data[i])<<8 + uint32(data[i+1])
    }
    
    // 奇数字节
    if len(data)%2 == 1 {
        sum += uint32(data[len(data)-1]) << 8
    }
    
    // 回卷(将进位加回低位)
    for (sum >> 16) > 0 {
        sum = (sum & 0xFFFF) + (sum >> 16)
    }
    
    return uint16(^sum)  // 取反
}

4.2 确认应答 ACK —— "我收到了"

sequenceDiagram
    participant Sender as 发送方
    participant Receiver as 接收方
    
    Sender->>Receiver: 发送数据 [seq=100, len=50]
    Note over Receiver: 收到字节100-149
    
    Receiver->>Sender: ACK=1, ack=150<br/>(期待下一个字节150)
    
    Sender->>Receiver: 发送数据 [seq=150, len=50]
    Receiver->>Sender: ACK=1, ack=200
    
    Note over Sender,Receiver: 累积确认:ack=200<br/>表示200之前的全部收到

累积确认的优势:

  • ACK=200 表示 0-199 全部收到
  • 即使 ACK=150 丢了,收到 ACK=200 就知道 150 也收到了

4.3 超时重传 —— 没收到确认就重发

graph TD
    A[发送数据] --> B[启动定时器]
    B --> C{收到ACK?}
    C -->|是| D[取消定时器<br/>发送下一个]
    C -->|否| E{超时?}
    E -->|否| C
    E -->|是| F[重传数据<br/>重启定时器]
    F --> C
    
    style D fill:#90EE90
    style F fill:#FFE4B5

超时时间计算(RTO):

// 自适应超时算法(Jacobson/Karels算法)
type RTTEstimator struct {
    srtt    float64  // 平滑往返时间
    rttvar  float64  // 往返时间变化
    rto     float64  // 重传超时
}

func (e *RTTEstimator) Update(measuredRTT float64) {
    const alpha = 0.875
    const beta = 0.25
    
    if e.srtt == 0 {
        // 首次测量
        e.srtt = measuredRTT
        e.rttvar = measuredRTT / 2
    } else {
        // 更新平滑RTT
        e.rttvar = (1-beta)*e.rttvar + beta*abs(e.srtt-measuredRTT)
        e.srtt = (1-alpha)*e.srtt + alpha*measuredRTT
    }
    
    // RTO = SRTT + 4 * RTTVAR
    e.rto = e.srtt + 4*e.rttvar
    
    // 边界保护
    if e.rto < 1 {
        e.rto = 1
    }
    if e.rto > 60 {
        e.rto = 60  // 最大60秒
    }
}

4.4 序号机制 —— 保证顺序

graph LR
    A[发送方发送] --> B[seq=100<br/>seq=200<br/>seq=150]
    B --> C[网络乱序到达]
    C --> D[接收方缓存]
    D --> E[按序号排序]
    E --> F[seq=100<br/>seq=150<br/>seq=200]
    F --> G[交付应用层]
    
    style B fill:#FFB6C1
    style F fill:#90EE90

4.5 去重 —— 丢弃重复数据

graph TD
    A[收到数据包] --> B{序号在接收窗口内?}
    B -->|否| C[丢弃]
    B -->|是| D{已收到?}
    D -->|是| E[丢弃重复]
    D -->|否| F[接收并缓存]
    
    style C fill:#FFB6C1
    style E fill:#FFB6C1
    style F fill:#90EE90

第五章:流量控制 —— 防止"撑死"接收方

5.1 滑动窗口 —— 动态调节发送速度

graph LR
    subgraph "发送窗口"
        A1[已发送<br/>已确认] --> B1[已发送<br/>未确认]
        B1 --> C1[允许发送<br/>未发送]
        C1 --> D1[不允许发送]
    end
    
    subgraph "窗口滑动"
        E1[收到ACK<br/>窗口右移] --> F1[可发送新数据]
    end
    
    style B1 fill:#FFE4B5
    style C1 fill:#90EE90

5.2 接收方通告窗口 rwnd

sequenceDiagram
    participant Sender as 发送方
    participant Receiver as 接收方
    
    Note over Receiver: 接收缓存:总大小 1000<br/>已用 300,剩余 700
    
    Receiver->>Sender: ACK=1, ack=501, rwnd=700<br/>(我还能收700字节)
    
    Sender->>Receiver: 发送 700 字节
    
    Note over Receiver: 应用层读取 200 字节<br/>缓存剩余 400
    
    Receiver->>Sender: ACK=1, ack=1201, rwnd=400<br/>(我还能收400字节)
    
    Sender->>Receiver: 发送 400 字节
    
    Note over Receiver: 应用层读取 500 字节<br/>缓存剩余 500
    
    Receiver->>Sender: ACK=1, ack=1601, rwnd=500<br/>(窗口更新了!)

Go 示例:设置接收窗口

package main

import (
    "fmt"
    "net"
    "syscall"
)

func main() {
    listener, _ := net.Listen("tcp", ":8080")
    conn, _ := listener.Accept()
    
    // 获取底层 TCP 连接
    tcpConn := conn.(*net.TCPConn)
    
    // 设置接收缓冲区大小(影响窗口)
    tcpConn.SetReadBuffer(65536)  // 64KB
    
    // 获取实际窗口大小
    file, _ := tcpConn.File()
    fd := file.Fd()
    
    // 使用 syscall 获取 socket 选项
    rcvbuf, _ := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF)
    sndbuf, _ := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUF)
    
    fmt.Printf("接收缓冲区: %d bytes\n", rcvbuf)
    fmt.Printf("发送缓冲区: %d bytes\n", sndbuf)
}

5.3 零窗口与窗口探测

sequenceDiagram
    participant Sender as 发送方
    participant Receiver as 接收方
    
    Receiver->>Sender: ACK, rwnd=0<br/>(我满了,别发了!)
    
    Note over Sender: 停止发送,启动持续定时器
    
    Sender->>Receiver: 窗口探测报文<br/>(你还满吗?)
    
    Receiver->>Sender: ACK, rwnd=0<br/>(还是满的...)
    
    Note over Sender: 持续定时器翻倍
    
    Sender->>Receiver: 窗口探测报文
    
    Note over Receiver: 应用层读取数据<br/>缓存有空间了
    
    Receiver->>Sender: ACK, rwnd=1000<br/>(可以发了!)
    
    Sender->>Receiver: 发送数据

第六章:拥塞控制 —— 防止"撑死"网络

流量控制:防止发送方过快压垮接收方 拥塞控制:防止发送方过快压垮网络

graph TD
    A[拥塞控制] --> B[慢启动 Slow Start]
    A --> C[拥塞避免 Congestion Avoidance]
    A --> D[快重传 Fast Retransmit]
    A --> E[快恢复 Fast Recovery]
    
    style A fill:#FFEBEE

6.1 慢启动 —— 试探网络容量

graph LR
    A["初始 cwnd=1 MSS"] --> B["每收到一个ACK<br/>cwnd += 1"]
    B --> C["RTT 1: cwnd=1<br/>发1个"]
    C --> D["RTT 2: cwnd=2<br/>发2个"]
    D --> E["RTT 3: cwnd=4<br/>发4个"]
    E --> F["RTT 4: cwnd=8<br/>发8个"]
    F --> G["指数增长!<br/>直到阈值"]
    
    style G fill:#90EE90

6.2 拥塞避免 —— 线性增长

graph LR
    A[cwnd > ssthresh] --> B[每轮RTT<br/>cwnd += 1]
    B --> C[线性增长<br/>更保守]
    C --> D[直到拥塞]
    
    style B fill:#FFE4B5

6.3 拥塞发生时的处理

graph TD
    A[检测到拥塞] --> B{拥塞信号?}
    B -->|超时| C[ssthresh = cwnd/2<br/>cwnd = 1<br/>慢启动]
    B -->|3个重复ACK| D[ssthresh = cwnd/2<br/>cwnd = ssthresh<br/>快恢复]
    
    style C fill:#FFB6C1
    style D fill:#90EE90

6.4 快重传 —— 不等超时

sequenceDiagram
    participant Sender as 发送方
    participant Receiver as 接收方
    
    Sender->>Receiver: seq=100
    Sender->>Receiver: seq=200
    Sender->>Receiver: seq=300(丢失!)
    Sender->>Receiver: seq=400
    
    Receiver->>Sender: ACK=300(重复,期待300)
    Receiver->>Sender: ACK=300(重复)
    Receiver->>Sender: ACK=300(重复!)
    
    Note over Sender: 收到3个重复ACK<br/>不等超时,立即重传300
    
    Sender->>Receiver: seq=300(快重传)
    
    Receiver->>Sender: ACK=500(累积确认)
    
    Note over Sender,Receiver: 快重传节省了<br/>等待超时的时间!

6.5 快恢复 —— 不降到1

graph LR
    A[快重传后] --> B["cwnd = ssthresh<br/>不是1!"]
    B --> C[拥塞避免<br/>线性增长]
    C --> D[更快恢复<br/>传输速度]
    
    style B fill:#90EE90

完整拥塞控制状态机:

stateDiagram-v2
    [*] --> SlowStart : 连接建立/超时恢复
    
    SlowStart --> CongestionAvoidance : cwnd >= ssthresh
    SlowStart --> SlowStart : 每ACK<br/>cwnd *= 2
    
    CongestionAvoidance --> CongestionAvoidance : 每RTT<br/>cwnd += 1
    CongestionAvoidance --> FastRecovery : 3个重复ACK
    
    FastRecovery --> CongestionAvoidance : 新ACK确认<br/>丢失包之后的数据
    FastRecovery --> SlowStart : 超时
    
    CongestionAvoidance --> SlowStart : 超时<br/>ssthresh=cwnd/2<br/>cwnd=1
    
    note right of SlowStart
        慢启动:指数增长
        探测网络容量
    end note
    
    note right of CongestionAvoidance
        拥塞避免:线性增长
        谨慎利用带宽
    end note
    
    note right of FastRecovery
        快恢复:不降到1
        快速恢复传输
    end note

Go 示例:查看 TCP 拥塞控制参数

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    // Linux 下查看当前拥塞控制算法
    out, _ := exec.Command("sysctl", "net.ipv4.tcp_congestion_control").Output()
    fmt.Printf("当前拥塞控制算法: %s", out)
    // 输出: net.ipv4.tcp_congestion_control = cubic
    
    // 查看可用的拥塞控制算法
    out, _ = exec.Command("sysctl", "net.ipv4.tcp_available_congestion_control").Output()
    fmt.Printf("可用算法: %s", out)
    // 输出: reno cubic bbr ...
    
    // 查看 TCP 连接状态
    out, _ = exec.Command("ss", "-ti").Output()
    fmt.Printf("TCP连接详情:\n%s", out)
    // 输出包含: cwnd, ssthresh, rtt 等信息
}

TCP 完整生命周期

sequenceDiagram
    participant App as 应用层
    participant TCP as TCP层
    participant IP as IP层
    participant Net as 网络
    
    Note over App,Net: === 连接建立 ===
    
    App->>TCP: connect()
    TCP->>TCP: 三次握手
    TCP->>IP: SYN包
    IP->>Net: 发送
    Net-->>IP: SYN+ACK
    IP-->>TCP: 
    TCP->>IP: ACK
    IP->>Net: 发送
    TCP-->>App: 连接建立!
    
    Note over App,Net: === 数据传输 ===
    
    App->>TCP: write(data)
    TCP->>TCP: 分段 + 加序号 + 计算校验和
    TCP->>TCP: 放入发送窗口
    TCP->>IP: 数据包
    IP->>Net: 发送
    
    Net-->>IP: ACK(确认)
    IP-->>TCP: 
    TCP->>TCP: 滑动窗口<br/>拥塞控制
    
    Note over App,Net: === 连接关闭 ===
    
    App->>TCP: close()
    TCP->>TCP: 四次挥手
    TCP->>IP: FIN包
    IP->>Net: 发送
    Net-->>IP: ACK
    IP-->>TCP: 
    Net-->>IP: FIN
    IP-->>TCP: 
    TCP->>IP: ACK
    IP->>Net: 发送
    TCP-->>App: 连接关闭
    
    Note over TCP: TIME_WAIT<br/>等待2MSL

TCP vs UDP 对比总结

特性TCPUDP
连接面向连接无连接
可靠性可靠(不丢、不重、有序)不可靠
传输方式字节流数据报
拥塞控制
流量控制
头部开销20字节8字节
速度
适用场景文件传输、HTTP、邮件视频直播、DNS、游戏
graph LR
    A[选择协议] --> B{需要可靠?}
    B -->|是| C[TCP<br/>HTTP/FTP/SSH]
    B -->|否| D{需要速度?}
    D -->|是| E[UDP<br/>直播/游戏/DNS]
    D -->|否| F[看场景]
    
    style C fill:#90EE90
    style E fill:#90EE90

面试高频

问题答案要点
为什么三次握手?确认双方收发能力;防止历史连接
为什么四次挥手?全双工,双方各自关闭;被动方可能有数据要发
TIME_WAIT作用?确保ACK到达;防止旧报文干扰新连接
滑动窗口原理?动态调整发送量,匹配接收方处理能力
拥塞控制四个算法?慢启动、拥塞避免、快重传、快恢复
流量控制 vs 拥塞控制?前者防接收方过载,后者防网络过载

tcp是可靠的传输协议,结合日常生活把这些概念套进去,就想象自己如果碰到这样的问题会如何处理,这样更易理解 TCP 是互联网的基石,理解它就像理解快递系统的运作原理——从下单(握手)、打包(分段)、运输(传输)、签收(确认)到退货(挥手),每个环节都精心设计,确保数据安全、有序、高效地到达目的地!