工作之余,复盘下TCP/IP协议栈。目的为更深层次理解其设计哲学、关键机制以及开发生涯中可能被“黑盒化”的细节。
首先明确下,TCP/IP不是一个协议,而是一个协议族,它的核心是分层设计。每一层只关心自己层的事,通过标准的接口为上层提供服务。
应用层:HTTP、FTP、SMTP、DNS等
传输层:TCP、UDP
网络层:IP、ICMP
网络接口层:Ethernet、WIFI
数据“流动”过程为:你的HTTP数据----->被加上TCP投----->被加上IP头----->被加上以太网头/MAC地址----->变成比特流----->仍到网络上。
应用层协议点睛
HTTP/1.1:持久连接、管道化(但存在队头阻塞)。
HTTP/2:二进制分帧、多路复用(解决队头阻塞)、头部压缩、服务器推送。
HTTPS = HTTP + TLS/SSL。TLS握手(非对称加密交换密钥) -> 对称加密通信。
DNS:UDP协议,端口53。递归查询 vs 迭代查询。DNS缓存是性能关键。
传输层核心:TCP和UDP
这是老生常谈,但我们深入一下。
UDP协议:
UDP属极简主义(关键数据结构仅包含:源端口、目标端口、长度、校验和),主张无连接、不可靠、尽最大努力交付。“不可靠”是特性,不是BUG,他把复杂性交给了应用层,换来了极低的延迟和开销。
TCP协议:
TCP属完美主义,TCP的核心是在不可靠的IP层之上,建立一个可靠的、面向连接的、字节流的通道。
三次握手 (Three-way Handshake) - 建立连接

(为什么是三次,不是两次?)
核心目的:确认双方的收发能力都正常,并同步初始序列号 (ISN)。防止已失效的连接请求报文:如果是两次握手,一个延迟的SYN包可能会让Server单方面建立连接,造成资源浪费。
过程:
Client -> Server: SYN=1, seq=X (Client说:“我能发,我的序列号从X开始。”)
Server -> Client: SYN=1, ACK=1, seq=Y, ack=X+1 (Server说:“我能收也能发。我收到了你的X,期待你下一个发X+1。我的序列号从Y开始。”)
Client -> Server: ACK=1, seq=X+1, ack=Y+1 (Client说:“好的,我收到了你的Y。这是我们第一次有效数据传输,我发的是X+1。”)
四次挥手 (Four-way Wavehand) - 断开连接

为什么是四次?
核心原因:TCP连接是全双工的,每一方向必须单独关闭。
过程:
Client -> Server: FIN=1, seq=u (Client说:“我数据发完了,要关闭我到你方向的连接。”)
Server -> Client: ACK=1, seq=v, ack=u+1 (Server说:“收到你的FIN了。”) -> 此时进入CLOSE_WAIT状态,Server可能还有数据要发给Client。
(Server处理完剩余数据) Server -> Client: FIN=1, ACK=1, seq=w, ack=u+1 (Server说:“我这边数据也发完了,我也要关了。”)
Client -> Server: ACK=1, seq=u+1, ack=w+1 (Client说:“收到,再见。”) -> Client进入TIME_WAIT状态。
关键机制
序列号与确认应答 (ACK):每个字节都有编号。接收方通过ACK告知发送方“我期望收到的下一个字节的序列号”,这是累积确认。
超时重传:发送一个数据段后启动定时器,如果超时未收到ACK,则重传。
流量控制 (Flow Control) - 解决接收方处理不过来的问题
通过 滑动窗口 (Sliding Window) 和 接收窗口 (rwnd) 实现。
接收方在ACK包中通告自己的接收窗口大小,表示自己还能处理多少数据。发送方发送的数据不能超过这个窗口。
拥塞控制 (Congestion Control) - 解决网络处理不过来(拥堵)的问题
发送方维护一个 拥塞窗口 (cwnd)。实际发送窗口 = min(rwnd, cwnd)。
核心算法:
慢启动 (Slow Start):连接开始时,cwnd指数增长(每收到一个ACK,cwnd+1)。
拥塞避免 (Congestion Avoidance):当cwnd超过慢启动阈值(ssthresh)后,转为线性增长(每RTT时间,cwnd+1)。
拥塞发生:
超时:认为网络非常拥堵。ssthresh降为cwnd/2,cwnd重置为1,重新慢启动。
收到3个重复ACK (Fast Retransmit):认为只是丢了个别包。ssthresh = cwnd/2, cwnd = ssthresh + 3,然后进入快速恢复 (Fast Recovery),线性增长,收到新数据的ACK后退出。
4. TIME_WAIT状态
为什么需要TIME_WAIT? 持续时间通常是2MSL (Maximum Segment Lifetime)。
可靠地终止连接:确保最后的ACK能到达对方。如果ACK丢失,对方会重发FIN,你还在TIME_WAIT,可以重发ACK。
让旧连接的报文在网络中消逝:防止具有相同四元组(源IP、源端口、目标IP、目标端口)的旧连接数据包干扰新连接。
实际问题:高并发短连接服务器上,大量连接处于TIME_WAIT状态,可能导致端口耗尽。解决方案包括开启 SO_REUSEADDR 套接字选项,允许内核重用处于TIME_WAIT状态的连接的端口。
网络层核心:IP协议
IP是无连接的:发送前不需要握手,每个数据包独立路由。
MTU (Maximum Transmission Unit):数据链路层能承载的最大数据量。超过MTU的IP数据包需要分片 (Fragmentation)。PMTUD (Path MTU Discovery) 机制用于发现路径上的最小MTU,避免分片(因为分片重组消耗资源,且任何一片丢失整个IP包作废)。
NAT (Network Address Translation):解决IPv4地址枯竭的核心技术。内网IP通过一个公网IP出口。
NAT打洞:P2P应用的基石。通过一个公共服务器协调,让两个都在NAT后的主机建立直接连接。