眉清目秀原来是平铺直叙 
TCP 协议:从握手到数据传输
序章:TCP 是什么?------ 网络世界的"可靠快递"
tcp是可靠的传输协议,结合日常生活把这些概念套进去,就想象自己如果碰到这样的问题会如何处理,这样更易理解,不用死记硬背。 想象你要寄一份重要合同给朋友:
- UDP 像普通明信片:便宜、快,但可能丢失、乱序、重复
- TCP 像顺丰快递:贵一点、慢一点,但保证送达、按顺序、不重复
TCP(Transmission Control Protocol,传输控制协议) 是互联网最核心的协议之一,位于传输层,提供面向连接、可靠、基于字节流的通信服务。
graph TD
A[应用层
HTTP/FTP/DNS] --> B[传输层
TCP/UDP] B --> C[网络层
IP] C --> D[数据链路层
以太网] D --> E[物理层
光纤/电缆] style B fill:#90EE90
HTTP/FTP/DNS] --> B[传输层
TCP/UDP] B --> C[网络层
IP] C --> D[数据链路层
以太网] D --> E[物理层
光纤/电缆] style B fill:#90EE90
第一章:TCP 特点 ------ 五大核心特性
mindmap
root((TCP
五大特点)) 面向连接 通信前先建立连接 像打电话要拨号 可靠传输 不丢包 不错乱 不重复 全双工通信 双方可同时发送和接收 像双向车道 字节流服务 不保留消息边界 像水流连续不断 点对点 一对一通信 不支持广播
五大特点)) 面向连接 通信前先建立连接 像打电话要拨号 可靠传输 不丢包 不错乱 不重复 全双工通信 双方可同时发送和接收 像双向车道 字节流服务 不保留消息边界 像水流连续不断 点对点 一对一通信 不支持广播
1.1 面向连接 ------ 先打电话,再说话
sequenceDiagram
participant A as 客户端
participant B as 服务端
Note over A,B: UDP:直接喊话
"喂!有人吗?" A->>B: TCP:先建立连接 B->>A: 三次握手完成 Note over A,B: 现在可以可靠通信了 A->>B: 发送数据 B->>A: 确认收到
"喂!有人吗?" 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 字节流服务 ------ 像水流一样
go
// 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 连接是一对一的,每个连接由四元组唯一标识:
scss
(源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
(我能听到你吗?我的序号是x) Note over Client,Server: 第二次握手:B 确认自己能发能收,A 确认B能收发 Server->>Client: SYN=1, ACK=1, seq=y, ack=x+1
(我能听到!我的序号是y,期待你的x+1) Note over Client,Server: 第三次握手:A 确认自己能收,B 确认A能发 Client->>Server: ACK=1, seq=x+1, ack=y+1
(我也能听到!给你x+1,期待你的y+1) Note over Client,Server: 连接建立!双方确认:我能发,你能收;你能发,我能收
(我能听到你吗?我的序号是x) Note over Client,Server: 第二次握手:B 确认自己能发能收,A 确认B能收发 Server->>Client: SYN=1, ACK=1, seq=y, ack=x+1
(我能听到!我的序号是y,期待你的x+1) Note over Client,Server: 第三次握手:A 确认自己能收,B 确认A能发 Client->>Server: ACK=1, seq=x+1, ack=y+1
(我也能听到!给你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 为什么不能两次握手?

Go 代码示例:TCP 连接建立
go
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]))
}
输出:
arduino
服务端:等待连接...
客户端:连接建立!本地 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
(A:我说完了,要关闭发送) Server->>Client: ACK=1, seq=v, ack=u+1
(B:知道了,但我还没说完) Note over Client,Server: B 继续发送剩余数据... Server->>Client: FIN=1, seq=w, ack=u+1
(B:我也说完了,要关闭发送) Client->>Server: ACK=1, seq=u+1, ack=w+1
(A:知道了,等待2MSL后关闭) Note over Client,Server: 连接完全关闭
(A:我说完了,要关闭发送) Server->>Client: ACK=1, seq=v, ack=u+1
(B:知道了,但我还没说完) Note over Client,Server: B 继续发送剩余数据... Server->>Client: FIN=1, seq=w, ack=u+1
(B:我也说完了,要关闭发送) Client->>Server: ACK=1, seq=u+1, ack=w+1
(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?

TCP 状态转换全景图:
stateDiagram-v2
[*] --> CLOSED
CLOSED --> SYN_SENT : 主动打开
发送SYN CLOSED --> LISTEN : 被动打开 LISTEN --> SYN_RCVD : 收到SYN
发送SYN+ACK SYN_SENT --> ESTABLISHED : 收到SYN+ACK
发送ACK SYN_RCVD --> ESTABLISHED : 收到ACK ESTABLISHED --> FIN_WAIT_1 : 主动关闭
发送FIN ESTABLISHED --> CLOSE_WAIT : 收到FIN
发送ACK FIN_WAIT_1 --> FIN_WAIT_2 : 收到ACK FIN_WAIT_1 --> TIME_WAIT : 收到FIN+ACK
发送ACK(同时关闭) FIN_WAIT_2 --> TIME_WAIT : 收到FIN
发送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
发送SYN CLOSED --> LISTEN : 被动打开 LISTEN --> SYN_RCVD : 收到SYN
发送SYN+ACK SYN_SENT --> ESTABLISHED : 收到SYN+ACK
发送ACK SYN_RCVD --> ESTABLISHED : 收到ACK ESTABLISHED --> FIN_WAIT_1 : 主动关闭
发送FIN ESTABLISHED --> CLOSE_WAIT : 收到FIN
发送ACK FIN_WAIT_1 --> FIN_WAIT_2 : 收到ACK FIN_WAIT_1 --> TIME_WAIT : 收到FIN+ACK
发送ACK(同时关闭) FIN_WAIT_2 --> TIME_WAIT : 收到FIN
发送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[数据正常
回复ACK] D -->|否| F[数据损坏
丢弃不回复
触发重传] style E fill:#90EE90 style F fill:#FFB6C1
回复ACK] D -->|否| F[数据损坏
丢弃不回复
触发重传] style E fill:#90EE90 style F fill:#FFB6C1
计算方式:将数据分成16位字,求和,取反码。
go
// 简化的校验和计算(示意)
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
(期待下一个字节150) Sender->>Receiver: 发送数据 [seq=150, len=50] Receiver->>Sender: ACK=1, ack=200 Note over Sender,Receiver: 累积确认:ack=200
表示200之前的全部收到
(期待下一个字节150) Sender->>Receiver: 发送数据 [seq=150, len=50] Receiver->>Sender: ACK=1, ack=200 Note over Sender,Receiver: 累积确认:ack=200
表示200之前的全部收到
累积确认的优势:
- ACK=200 表示 0-199 全部收到
- 即使 ACK=150 丢了,收到 ACK=200 就知道 150 也收到了
4.3 超时重传 ------ 没收到确认就重发
graph TD
A[发送数据] --> B[启动定时器]
B --> C{收到ACK?}
C -->|是| D[取消定时器
发送下一个] C -->|否| E{超时?} E -->|否| C E -->|是| F[重传数据
重启定时器] F --> C style D fill:#90EE90 style F fill:#FFE4B5
发送下一个] C -->|否| E{超时?} E -->|否| C E -->|是| F[重传数据
重启定时器] F --> C style D fill:#90EE90 style F fill:#FFE4B5
超时时间计算(RTO):
go
// 自适应超时算法(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
seq=200
seq=150] B --> C[网络乱序到达] C --> D[接收方缓存] D --> E[按序号排序] E --> F[seq=100
seq=150
seq=200] F --> G[交付应用层] style B fill:#FFB6C1 style F fill:#90EE90
seq=200
seq=150] B --> C[网络乱序到达] C --> D[接收方缓存] D --> E[按序号排序] E --> F[seq=100
seq=150
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[已发送
已确认] --> B1[已发送
未确认] B1 --> C1[允许发送
未发送] C1 --> D1[不允许发送] end subgraph "窗口滑动" E1[收到ACK
窗口右移] --> F1[可发送新数据] end style B1 fill:#FFE4B5 style C1 fill:#90EE90
已确认] --> B1[已发送
未确认] B1 --> C1[允许发送
未发送] C1 --> D1[不允许发送] end subgraph "窗口滑动" E1[收到ACK
窗口右移] --> F1[可发送新数据] end style B1 fill:#FFE4B5 style C1 fill:#90EE90
5.2 接收方通告窗口 rwnd
sequenceDiagram
participant Sender as 发送方
participant Receiver as 接收方
Note over Receiver: 接收缓存:总大小 1000
已用 300,剩余 700 Receiver->>Sender: ACK=1, ack=501, rwnd=700
(我还能收700字节) Sender->>Receiver: 发送 700 字节 Note over Receiver: 应用层读取 200 字节
缓存剩余 400 Receiver->>Sender: ACK=1, ack=1201, rwnd=400
(我还能收400字节) Sender->>Receiver: 发送 400 字节 Note over Receiver: 应用层读取 500 字节
缓存剩余 500 Receiver->>Sender: ACK=1, ack=1601, rwnd=500
(窗口更新了!)
已用 300,剩余 700 Receiver->>Sender: ACK=1, ack=501, rwnd=700
(我还能收700字节) Sender->>Receiver: 发送 700 字节 Note over Receiver: 应用层读取 200 字节
缓存剩余 400 Receiver->>Sender: ACK=1, ack=1201, rwnd=400
(我还能收400字节) Sender->>Receiver: 发送 400 字节 Note over Receiver: 应用层读取 500 字节
缓存剩余 500 Receiver->>Sender: ACK=1, ack=1601, rwnd=500
(窗口更新了!)
Go 示例:设置接收窗口
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
(我满了,别发了!) Note over Sender: 停止发送,启动持续定时器 Sender->>Receiver: 窗口探测报文
(你还满吗?) Receiver->>Sender: ACK, rwnd=0
(还是满的...) Note over Sender: 持续定时器翻倍 Sender->>Receiver: 窗口探测报文 Note over Receiver: 应用层读取数据
缓存有空间了 Receiver->>Sender: ACK, rwnd=1000
(可以发了!) Sender->>Receiver: 发送数据
(我满了,别发了!) Note over Sender: 停止发送,启动持续定时器 Sender->>Receiver: 窗口探测报文
(你还满吗?) Receiver->>Sender: ACK, rwnd=0
(还是满的...) Note over Sender: 持续定时器翻倍 Sender->>Receiver: 窗口探测报文 Note over Receiver: 应用层读取数据
缓存有空间了 Receiver->>Sender: ACK, rwnd=1000
(可以发了!) 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
cwnd += 1"] B --> C["RTT 1: cwnd=1
发1个"] C --> D["RTT 2: cwnd=2
发2个"] D --> E["RTT 3: cwnd=4
发4个"] E --> F["RTT 4: cwnd=8
发8个"] F --> G["指数增长!
直到阈值"] style G fill:#90EE90
cwnd += 1"] B --> C["RTT 1: cwnd=1
发1个"] C --> D["RTT 2: cwnd=2
发2个"] D --> E["RTT 3: cwnd=4
发4个"] E --> F["RTT 4: cwnd=8
发8个"] F --> G["指数增长!
直到阈值"] style G fill:#90EE90
6.2 拥塞避免 ------ 线性增长
graph LR
A[cwnd > ssthresh] --> B[每轮RTT
cwnd += 1] B --> C[线性增长
更保守] C --> D[直到拥塞] style B fill:#FFE4B5
cwnd += 1] B --> C[线性增长
更保守] C --> D[直到拥塞] style B fill:#FFE4B5
6.3 拥塞发生时的处理
graph TD
A[检测到拥塞] --> B{拥塞信号?}
B -->|超时| C[ssthresh = cwnd/2
cwnd = 1
慢启动] B -->|3个重复ACK| D[ssthresh = cwnd/2
cwnd = ssthresh
快恢复] style C fill:#FFB6C1 style D fill:#90EE90
cwnd = 1
慢启动] B -->|3个重复ACK| D[ssthresh = cwnd/2
cwnd = ssthresh
快恢复] 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
不等超时,立即重传300 Sender->>Receiver: seq=300(快重传) Receiver->>Sender: ACK=500(累积确认) Note over Sender,Receiver: 快重传节省了
等待超时的时间!
不等超时,立即重传300 Sender->>Receiver: seq=300(快重传) Receiver->>Sender: ACK=500(累积确认) Note over Sender,Receiver: 快重传节省了
等待超时的时间!
6.5 快恢复 ------ 不降到1
graph LR
A[快重传后] --> B["cwnd = ssthresh
不是1!"] B --> C[拥塞避免
线性增长] C --> D[更快恢复
传输速度] style B fill:#90EE90
不是1!"] B --> C[拥塞避免
线性增长] C --> D[更快恢复
传输速度] style B fill:#90EE90
完整拥塞控制状态机:
stateDiagram-v2
[*] --> SlowStart : 连接建立/超时恢复
SlowStart --> CongestionAvoidance : cwnd >= ssthresh
SlowStart --> SlowStart : 每ACK
cwnd *= 2 CongestionAvoidance --> CongestionAvoidance : 每RTT
cwnd += 1 CongestionAvoidance --> FastRecovery : 3个重复ACK FastRecovery --> CongestionAvoidance : 新ACK确认
丢失包之后的数据 FastRecovery --> SlowStart : 超时 CongestionAvoidance --> SlowStart : 超时
ssthresh=cwnd/2
cwnd=1 note right of SlowStart 慢启动:指数增长 探测网络容量 end note note right of CongestionAvoidance 拥塞避免:线性增长 谨慎利用带宽 end note note right of FastRecovery 快恢复:不降到1 快速恢复传输 end note
cwnd *= 2 CongestionAvoidance --> CongestionAvoidance : 每RTT
cwnd += 1 CongestionAvoidance --> FastRecovery : 3个重复ACK FastRecovery --> CongestionAvoidance : 新ACK确认
丢失包之后的数据 FastRecovery --> SlowStart : 超时 CongestionAvoidance --> SlowStart : 超时
ssthresh=cwnd/2
cwnd=1 note right of SlowStart 慢启动:指数增长 探测网络容量 end note note right of CongestionAvoidance 拥塞避免:线性增长 谨慎利用带宽 end note note right of FastRecovery 快恢复:不降到1 快速恢复传输 end note
Go 示例:查看 TCP 拥塞控制参数
go
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: 滑动窗口
拥塞控制 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
等待2MSL
拥塞控制 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
等待2MSL
TCP vs UDP 对比总结
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠(不丢、不重、有序) | 不可靠 |
| 传输方式 | 字节流 | 数据报 |
| 拥塞控制 | 有 | 无 |
| 流量控制 | 有 | 无 |
| 头部开销 | 20字节 | 8字节 |
| 速度 | 慢 | 快 |
| 适用场景 | 文件传输、HTTP、邮件 | 视频直播、DNS、游戏 |
graph LR
A[选择协议] --> B{需要可靠?}
B -->|是| C[TCP
HTTP/FTP/SSH] B -->|否| D{需要速度?} D -->|是| E[UDP
直播/游戏/DNS] D -->|否| F[看场景] style C fill:#90EE90 style E fill:#90EE90
HTTP/FTP/SSH] B -->|否| D{需要速度?} D -->|是| E[UDP
直播/游戏/DNS] D -->|否| F[看场景] style C fill:#90EE90 style E fill:#90EE90
面试高频
| 问题 | 答案要点 |
|---|---|
| 为什么三次握手? | 确认双方收发能力;防止历史连接 |
| 为什么四次挥手? | 全双工,双方各自关闭;被动方可能有数据要发 |
| TIME_WAIT作用? | 确保ACK到达;防止旧报文干扰新连接 |
| 滑动窗口原理? | 动态调整发送量,匹配接收方处理能力 |
| 拥塞控制四个算法? | 慢启动、拥塞避免、快重传、快恢复 |
| 流量控制 vs 拥塞控制? | 前者防接收方过载,后者防网络过载 |
tcp是可靠的传输协议,结合日常生活把这些概念套进去,就想象自己如果碰到这样的问题会如何处理,这样更易理解 TCP 是互联网的基石,理解它就像理解快递系统的运作原理------从下单(握手)、打包(分段)、运输(传输)、签收(确认)到退货(挥手),每个环节都精心设计,确保数据安全、有序、高效地到达目的地!
