TCP/IP 协议簇是互联网的基石,是每个工程师都应该掌握的知识。这篇文章将系统地回顾 TCP/IP 协议簇的核心内容。

OSI 模型与 TCP/IP 模型

OSI 模型将网络通信分为七层,而 TCP/IP 模型则将其简化为四层。虽然 OSI 模型是理论标准,但实际应用中 TCP/IP 模型更为广泛。

OSI 七层模型与 TCP/IP 四层模型对应关系

graph TB
    subgraph OSI七层模型
        A[7. 应用层<br/>Application Layer]
        B[6. 表示层<br/>Presentation Layer]
        C[5. 会话层<br/>Session Layer]
        D[4. 传输层<br/>Transport Layer]
        E[3. 网络层<br/>Network Layer]
        F[2. 数据链路层<br/>Data Link Layer]
        G[1. 物理层<br/>Physical Layer]
    end
    
    subgraph TCP/IP四层模型
        H[4. 应用层<br/>Application Layer]
        I[3. 传输层<br/>Transport Layer]
        J[2. 网际层<br/>Internet Layer]
        K[1. 网络接口层<br/>Network Interface Layer]
    end
    
    A --> H
    B --> H
    C --> H
    D --> I
    E --> J
    F --> K
    G --> K

OSI 模型从上到下依次为:

  1. 应用层(Application Layer)
  2. 表示层(Presentation Layer)
  3. 会话层(Session Layer)
  4. 传输层(Transport Layer)
  5. 网络层(Network Layer)
  6. 数据链路层(Data Link Layer)
  7. 物理层(Physical Layer)

TCP/IP 模型对应的四层为:

  1. 应用层(Application Layer)
  2. 传输层(Transport Layer)
  3. 网际层(Internet Layer)
  4. 网络接口层(Network Interface Layer)

各层功能说明

OSI 层级 TCP/IP 层级 主要功能 典型协议
应用层 应用层 为应用程序提供网络服务 HTTP, FTP, SMTP, DNS
表示层 应用层 数据格式化、加密、压缩 SSL/TLS, JPEG, ASCII
会话层 应用层 建立、管理和终止会话 RPC, NetBIOS
传输层 传输层 端到端通信、可靠性保证 TCP, UDP
网络层 网际层 路由选择、逻辑寻址 IP, ICMP, IGMP
数据链路层 网络接口层 物理寻址、错误检测 Ethernet, PPP, ARP
物理层 网络接口层 比特传输、物理接口 光纤、双绞线、无线电

IP 协议

IP(Internet Protocol)是网际层的核心协议,负责在网络中寻址和路由数据包。

IP 报文格式

字段 位数 说明
版本(Version) 4 IP 协议版本(IPv4 为 4)
首部长度(IHL) 4 IP 首部的长度(以 32 位字为单位)
服务类型(TOS) 8 服务质量和优先级
总长度(Total Length) 16 IP 首部加数据的总长度
标识(Identification) 16 唯一标识一个 IP 数据报
标志(Flags) 3 分片控制标志(DF, MF)
片偏移(Fragment Offset) 13 分片在原始数据报中的位置
生存时间(TTL) 8 数据报在网络中的生存时间
协议(Protocol) 8 上层协议类型(TCP=6, UDP=17)
首部校验和(Header Checksum) 16 首部的错误检测
源地址(Source Address) 32 发送方的 IP 地址
目的地址(Destination Address) 32 接收方的 IP 地址
选项(Options) 可变 可选的额外信息

IP 地址与子网

IP 地址分为 IPv4 和 IPv6 两种。IPv4 地址为 32 位,通常用点分十进制表示(如 192.168.1.1)。IPv6 地址为 128 位,用冒号分隔的十六进制表示。

子网掩码用于划分网络部分和主机部分。常见的子网掩码有 255.255.255.0(/24)、255.255.0.0(/16)等。

路由器与路由表

packet 在子网中传输通常大部分情况下只经过路由器,只在到达最后一个目标子网以后由那里的路由器跳到目标机器上。

路由器内部会维持一个路由表,用高速缓存机制来确认某几个特定子网的 packet 应该去往哪个子网。

ARP 协议与 RARP 协议

ARP 协议大概可以理解为一个子网内的广播协议。就是一个 Host 想要知道一个 IP 地址对应的 Mac 地址是多少,向本子网进行一个 broadcasting,寻找答案。配合上面对 IP 协议的解读,基本上就是一个本子网内才会用到的协议。而 RARP 协议则正好反过来。

ICMP(Internet Control Message Protocol) 协议

当传送IP数据包发生错误,比如主机不可达/路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。这导致了我们可以通过一些应用来诊断网络状况。

ping

ping这个单词源自声纳定位,而这个程序的作用也确实如此,它利用ICMP协议包来侦测另一个主机是否可达。原理是用类型码为8(Echo Request)的ICMP发请求,受到请求的主机则用类型码为0(Echo Reply)的ICMP回应。

ping程序来计算间隔时间,并计算有多少个包被送达。用户就可以判断网络大致的情况。我们可以看到, ping给出来了传送的时间和TTL的数据。

Traceroute

这个工具在不同 OS 上的缩写是不一样的,linux系统中是traceroute,在MS Windows中是tracert。

traceroute 的名称清楚地表明了这是一个追踪路由器工作过程的工具。

它的工作原理是:

traceroute 在收到目标地址以后,向目标地址发送一个 TTL 为1的 UDP packet,当第一个路由器收到这个 packet 以后,自动把 TTL 减为0,而TTL为0的 packet 已然无法向前(所以TTL为1的packet一开始能去哪儿呢?),路由器就扔掉它,并返回ICMP Time Exceeded 报文来报告源主机网络不可达。traceroute在收到 ICMP 报文后,又再发送一条 UDP报文,如此循环往复,直到最后一个 packet 到达目标地址,返回ICMP Echo Reply报文。

根据英文维基百科,实际上任意形式的协议报文都可以拿来做这种探针报文,当然通常我们使用 payload 无意义的 UDP packet。

UDP(User Datagram Protocol)协议

UDP 是传输层的另一个核心协议,与 TCP 形成互补关系。

UDP 报文格式

UDP 的报文格式极其简洁,首部仅有 8 个字节,包含四个字段:

  • 源端口号(16位):发送方端口
  • 目的端口号(16位):接收方端口
  • 长度(16位):UDP 首部加数据的总长度
  • 校验和(16位):可选的错误检测(在 IPv4 中可选,在 IPv6 中强制)

相比 TCP 首部至少 20 字节(不含选项),UDP 的 8 字节首部开销极小。

UDP 的核心特征

UDP 是一个无连接、不可靠的传输协议,其设计哲学是 尽可能少地在传输层引入开销

特征 UDP TCP
连接管理 无连接,无需握手 面向连接,三次握手
可靠性 不保证送达、不保证顺序 保证送达、保证顺序
流量控制 滑动窗口
拥塞控制 慢启动、拥塞避免等
首部开销 8 字节 至少 20 字节
传输模式 基于数据报(datagram) 基于字节流(byte stream)

UDP 的适用场景

UDP 适用于以下场景:

  • 实时性要求高于可靠性的场景:如视频会议、在线游戏、VoIP。丢失少量数据包比等待重传更可接受
  • 请求-响应模式的短交互:如 DNS 查询。一个请求一个响应,无需建立连接的开销
  • 广播和多播:UDP 天然支持一对多通信,TCP 不支持
  • 应用层自行实现可靠性的场景:如 QUIC 协议,在 UDP 之上实现了类似 TCP 的可靠传输,但具有更灵活的控制能力

基于 UDP 的常见协议

  • DNS(端口 53):域名解析查询通常使用 UDP(超过 512 字节时回退到 TCP)
  • DHCP(端口 67/68):动态主机配置
  • SNMP(端口 161/162):网络管理
  • RTP(Real-time Transport Protocol):实时音视频传输
  • QUIC:Google 设计的基于 UDP 的传输协议,已成为 HTTP/3 的底层传输协议。QUIC 在 UDP 之上实现了连接管理、可靠传输、流量控制和 TLS 加密,同时避免了 TCP 的队头阻塞(Head-of-Line Blocking)问题

TCP 协议

TCP 报文格式

TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议。

字段 位数 说明
源端口号 16 发送方端口
目的端口号 16 接收方端口
序号(Sequence Number) 32 数据字节流的序号
确认号(Acknowledgment Number) 32 期望收到的下一个字节的序号
首部长度(Data Offset) 4 TCP 首部的长度(以 32 位字为单位)
保留(Reserved) 6 保留字段,必须置 0
标志位(Flags) 6 控制标志(URG、ACK、PSH、RST、SYN、FIN)
窗口大小(Window Size) 16 接收窗口大小,用于流量控制
校验和(Checksum) 16 首部和数据的错误检测
紧急指针(Urgent Pointer) 16 紧急数据的偏移量
选项(Options) 可变 可选的额外信息(如 MSS、窗口扩大因子等)

标志位说明

  • URG:紧急指针(urgent pointer)有效
  • ACK:确认序号有效
  • PSH:接收方应该尽快将这个报文交给应用层
  • RST:重置连接
  • SYN:发起一个新连接
  • FIN:释放一个连接

协议栈封装与解封装

graph TB
    subgraph 发送方
        A[应用数据] --> B[添加TCP首部]
        B --> C[添加IP首部]
        C --> D[添加以太网首部]
        D --> E[物理传输]
    end
    
    subgraph 接收方
        E --> F[移除以太网首部]
        F --> G[移除IP首部]
        G --> H[移除TCP首部]
        H --> I[应用数据]
    end
    
    style E fill:#f9f,stroke:#333,stroke-width:2px

上层报文格式不需要知道下层的报文格式,下层的报文在拿到上层报文后,直接添加一段首部(在以太网层面还需要加入校验和作为尾部),即成为本层的报文。整体上还是一个 header + body(在网络协议里面通常叫做 payload)的模型。

报文在发送方的一端,看起来像是一个不断入栈的过程,而从接收方而言,就像是一个不断出栈的过程。虽然因为物理因素,发送方和接收方实际上是在操纵两个栈,但这两个栈理论上应该是对等的,发送方的报文穿过多层栈,接收方也会重建多层栈。

三次握手

sequenceDiagram
    participant Client
    participant Server
    
    Client->>Server: SYN=1, seq=x<br/>(SYN_SENT)
    Server->>Client: SYN=1, ACK=1, seq=y, ack=x+1<br/>(SYN_RCVD)
    Client->>Server: ACK=1, seq=x+1, ack=y+1<br/>(ESTABLISHED)
    Note over Server: (ESTABLISHED)

首先再来详细看看 TCP 当报文格式:

序号就是经常说的 seq(Sequence Number),占 32 位,因为 TCP 是面向字节流的(byte stream oriented),所以要给流中的一个数据包编号。

确认号就是 Acknowledgment Number(if ACK set),它只有在 ACK 标志位被置位以后才生效。它等于收到的 seq + 1,表示期望收到的下一个字节的序号。

而标志位的含义是:

  • URG:紧急指针(urgent pointer)有效
  • ACK:确认序号有效
  • PSH:接收方应该尽快将这个报文交给应用层
  • RST:重置连接
  • SYN:发起一个新连接
  • FIN:释放一个连接

三次握手的过程如下:

第一次握手:Client 把 SYN 标志位设置为 1,然后生成一个随机的 seq 作为通信的起点,发送以后本机进入 SYN_SENT 状态。

第二次握手:Server 在收到 SYN 报文以后,知道 Client 要建立连接,于是生成一个 SYN 和 ACK 标志位都为 1 的报文,其中 ack 号为收到的 seq 值加一(可以看出 ack 不仅表明了收到的报文的序号,也表明了期待接下来收到的 seq 的序号),然后随机产生一个值 seq=K(Client 和 Server 使用两个随机数来通信),并将该数据包发送给 Client 以确认连接请求,Server 进入 SYN_RCVD 状态。

第三次握手:Client 收到确认后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack=K+1,并将该数据包发送给 Server,Server 检查 ack 是否为 K+1,ACK 是否为 1,如果正确则连接建立成功,Client 和 Server 进入 ESTABLISHED 状态(由此可见,只有收到 ACK 为 1 的报文,并且 ack 为自己发送的值加一的报文的时候,一端才算进入 ESTABLISHED 状态),完成三次握手,随后 Client 与 Server 之间可以开始传输数据了。

三次握手的作用

  • 防止已失效的连接请求报文段突然又传送到了服务端
  • 同步双方的初始序列号
  • 确认双方的接收和发送能力

四次挥手

sequenceDiagram
    participant Client
    participant Server
    
    Client->>Server: FIN=1, seq=u<br/>(FIN_WAIT_1)
    Server->>Client: ACK=1, seq=v, ack=u+1<br/>(CLOSE_WAIT)
    Note over Client: (FIN_WAIT_2)
    Server->>Client: FIN=1, ACK=1, seq=w, ack=u+1<br/>(LAST_ACK)
    Client->>Server: ACK=1, seq=u+1, ack=w+1<br/>(TIME_WAIT)
    Note over Client: 等待 2MSL
    Note over Server: (CLOSED)
    Note over Client: (CLOSED)

由于 TCP 连接是全双工的,因此每个方向都必须要单独进行关闭。这一原则是当一方完成数据发送任务后,发送一个 FIN 来终止这一方向的连接,收到一个 FIN 只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个 TCP 连接上仍然能够发送数据,直到这一方向也发送了 FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

第一次挥手:Client 发送一个 FIN,用来关闭 Client 到 Server 的数据传送,Client 进入 FIN_WAIT_1 状态。

第二次挥手:Server 收到 FIN 后,发送一个 ACK 给 Client,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号),Server 进入 CLOSE_WAIT 状态。

第三次挥手:Server 发送一个 FIN,用来关闭 Server 到 Client 的数据传送,Server 进入 LAST_ACK 状态。

第四次挥手:Client 收到 FIN 后,Client 进入 TIME_WAIT 状态,接着发送一个 ACK 给 Server,确认序号为收到序号+1,Server 进入 CLOSED 状态,完成四次挥手。

TIME_WAIT 状态

TIME_WAIT 状态持续时间为 2MSL(Maximum Segment Lifetime,最大报文生存时间)。TIME_WAIT 状态的作用:

  • 确保最后一个 ACK 能够到达对方
  • 等待足够的时间,确保当前连接的所有报文都从网络中消失

由上述过程可以看出,握手和挥手都会用到 ACK 标志位和 ack 序号。它们确认的包的标志位是 SYN 还是 FIN 才是挥手和握手的主要区别。

为什么握手是三次而挥手是四次?

双工通信要求了我们建立连接和取消连接,其实是建立和取消两条管道(其实是两个 seq 序列)。

建立连接到时候必然要做成双工的,所以两端的握手必须紧密安排在一起,而第一个 ACK 和第二个 SYN 是可以为了简便而合二为一的。

挥手的时候,可以完全关闭,也可以双工变单工,特别是想要关闭连接的一方没有数据要发送了,不代表另一方的数据已经发送完了,因此不是那么适合把第一次关闭的 ACK 和第二次关闭的 FIN 合二为一。

有些时候,我们不喜欢TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。

TIME_WAIT 状态的处理

有些时候,不喜欢 TIME_WAIT 状态(如当 MSL 数值设置过大导致服务器端有太多 TIME_WAIT 状态的 TCP 连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时可以通过设置 SOCKET 变量的 SO_LINGER 标志来避免 SOCKET 在 close() 之后进入 TIME_WAIT 状态,这时将通过发送 RST 强制终止 TCP 连接(取代正常的 TCP 四次握手的终止方式)。

SO_LINGER 选项的结构:

  • l_onoff:是否启用 SO_LINGER
  • l_linger:等待时间(秒)

当 l_onoff 非 0 且 l_linger 为 0 时,调用 close() 会发送 RST 而不是 FIN,直接关闭连接,不进入 TIME_WAIT 状态。

TCP 的可靠性机制

TCP 通过以下机制保证可靠传输:

1. 序列号与确认应答

每个字节都有唯一的序列号,接收方收到数据后发送确认应答(ACK)。发送方收到 ACK 后,才能确认数据已被接收。

2. 重传机制

  • 超时重传:发送方在发送数据后启动定时器,如果在定时器到期前未收到 ACK,则重传数据
  • 快速重传:如果发送方连续收到三个重复的 ACK,立即重传对应的数据段,无需等待超时

3. 滑动窗口

滑动窗口用于流量控制,防止发送方发送速度过快导致接收方来不及处理。

窗口大小表示接收方当前可用的缓冲区大小。发送方根据窗口大小调整发送速率。

4. 拥塞控制

拥塞控制防止过多的数据注入到网络中,导致网络拥塞。拥塞控制算法包括:

  • 慢启动:连接建立时,拥塞窗口从 1 开始,每个 RTT 翻倍
  • 拥塞避免:拥塞窗口达到慢启动阈值后,每个 RTT 增加 1
  • 快重传:收到 3 个重复 ACK 时,立即重传
  • 快恢复:快重传后,将拥塞窗口减半,进入拥塞避免阶段

用 tcpdump 来查看tcp协议通信传输的过程

首先用ping 查看tianya的地址:

ping tianya.com
PING tianya.com (120.24.90.198): 56 data bytes
64 bytes from 120.24.90.198: icmp_seq=0 ttl=49 time=40.042 ms
64 bytes from 120.24.90.198: icmp_seq=1 ttl=49 time=37.354 ms
64 bytes from 120.24.90.198: icmp_seq=2 ttl=49 time=37.271 ms
64 bytes from 120.24.90.198: icmp_seq=3 ttl=49 time=39.367 ms
64 bytes from 120.24.90.198: icmp_seq=4 ttl=49 time=37.655 ms
64 bytes from 120.24.90.198: icmp_seq=5 ttl=49 time=37.335 ms

然后用以下命令开始监控:

1
2
tcpdump host 120.24.90.198
curl 120.24.90.198

得到的全部输出如下:

17:57:13.415883 IP bogon.65290 > 120.24.90.198.http: Flags [S], seq 477782453, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 213769344 ecr 0,sackOK,eol], length 0
17:57:13.452814 IP 120.24.90.198.http > bogon.65290: Flags [S.], seq 2061846025, ack 477782454, win 14600, options [mss 1386,nop,nop,sackOK,nop,wscale 7], length 0
17:57:13.452857 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 1, win 8192, length 0
17:57:13.452949 IP bogon.65290 > 120.24.90.198.http: Flags [P.], seq 1:78, ack 1, win 8192, length 77: HTTP: GET / HTTP/1.1
17:57:13.490112 IP 120.24.90.198.http > bogon.65290: Flags [.], ack 78, win 115, length 0
17:57:13.516730 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 1:1387, ack 78, win 115, length 1386: HTTP: HTTP/1.1 200 OK
17:57:13.516736 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 1387:2773, ack 78, win 115, length 1386: HTTP
17:57:13.516771 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 2773, win 8105, length 0
17:57:13.516826 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 2773, win 8192, length 0
17:57:13.517050 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 2773:4159, ack 78, win 115, length 1386: HTTP
17:57:13.517053 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 4159:5545, ack 78, win 115, length 1386: HTTP
17:57:13.517055 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 5545:6931, ack 78, win 115, length 1386: HTTP
17:57:13.517056 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 6931:8317, ack 78, win 115, length 1386: HTTP
17:57:13.517058 IP 120.24.90.198.http > bogon.65290: Flags [P.], seq 8317:9001, ack 78, win 115, length 684: HTTP
17:57:13.517079 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 5545, win 8105, length 0
17:57:13.517089 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 8317, win 8018, length 0
17:57:13.517095 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 9001, win 7997, length 0
17:57:13.517504 IP 120.24.90.198.http > bogon.65290: Flags [.], seq 9001:10387, ack 78, win 115, length 1386: HTTP
17:57:13.517507 IP 120.24.90.198.http > bogon.65290: Flags [P.], seq 10387:11135, ack 78, win 115, length 748: HTTP
17:57:13.517523 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 11135, win 7930, length 0
17:57:13.519626 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 11135, win 8192, length 0
17:57:13.526151 IP bogon.65290 > 120.24.90.198.http: Flags [F.], seq 78, ack 11135, win 8192, length 0
17:57:13.563681 IP 120.24.90.198.http > bogon.65290: Flags [F.], seq 11135, ack 79, win 115, length 0
17:57:13.563735 IP bogon.65290 > 120.24.90.198.http: Flags [.], ack 11136, win 8192, length 0

要注意:

  • 初始的seq数字往往都很大。

  • ack的结果是下一个希望收到的字节流的第一个字节的序号。比如这一次收到的字节是以10000个字节结尾的,ack显示的结果就需要下一段字节流以10001号字节开头。因为并不是每一个包都每次只发送一个字节,所以ack并不一定等于收到的包的seq加一,而是等于seq加上实际的length加一。

  • tcpdump 显示的seq号,只有一开始显示syn的时候的seq号是绝对seq号,接下来都是相对seq号。

  • 这个.是ACK的意思。原因见。缩写的含义是S (SYN), F (FIN), P (PUSH), R (RST), U(URG), W (ECN CWR), E (ECN-Echo) or '.' (ACK)

tcpdump 常用选项

  • -i interface:指定网络接口
  • -n:不解析主机名和端口号
  • -X:以十六进制和 ASCII 格式显示数据包内容
  • -vv:更详细的输出
  • -c count:只捕获指定数量的数据包

总结

TCP/IP 协议簇是互联网通信的基础,理解其核心协议的工作原理对于网络编程和系统设计至关重要。本文回顾了 OSI 模型与 TCP/IP 模型、IP 协议、ARP/RARP 协议、ICMP 协议、UDP 协议和 TCP 协议的核心内容,包括报文格式、工作原理和实际应用。

掌握 TCP/IP 协议不仅有助于理解网络通信的本质,还能帮助工程师更好地进行网络问题排查、性能优化和系统架构设计。