网络基础与 OSI/TCP-IP 协议栈

从零理解计算机网络的分层模型,掌握数据包的封装解封装过程、各层 PDU 与 MTU 的完整流转

理解网络编程的起点是理解协议栈的分层思想。每一层隐藏了底层的实现细节,通过标准化的接口为上层提供服务。

一、OSI 七层模型 vs TCP/IP 四层模型

1.1 分层对照

┌──────────────┬──────────────────────────────────┬──────────────────┐
│    OSI       │              功能                  │    TCP/IP        │
├──────────────┼──────────────────────────────────┼──────────────────┤
│ 7 应用层     │ HTTP、FTP、SSH、gRPC、WebSocket    │                  │
│ 6 表示层     │ 数据格式、加密解密、压缩           │   应用层         │
│ 5 会话层     │ 会话建立、管理、终止               │                  │
├──────────────┼──────────────────────────────────┼──────────────────┤
│ 4 传输层     │ TCP、UDP、端口号                   │   传输层         │
├──────────────┼──────────────────────────────────┼──────────────────┤
│ 3 网络层     │ IP、ICMP、路由选择、子网划分       │   互联网层       │
├──────────────┼──────────────────────────────────┼──────────────────┤
│ 2 数据链路层 │ MAC 地址、以太网、ARP、交换机      │   网络接口层     │
│ 1 物理层     │ 网线、光纤、无线、比特流传输       │                  │
└──────────────┴──────────────────────────────────┴──────────────────┘

1.2 分层数据交换逻辑

发送方                                         接收方
  │                                              ▲
  │  Application Data                            │  Application Data
  ▼                                              │
┌──────┐                                       ┌──────┐
│ HTTP  │  ← 应用层:HTTP 请求报文              │ HTTP │
├──────┤                                       ├──────┤
│ TCP   │  ← 传输层:Segment(段)               │ TCP  │
├──────┤                                       ├──────┤
│ IP    │  ← 网络层:Packet(包)                │ IP   │
├──────┤                                       ├──────┤
│ ETH   │  ← 链路层:Frame(帧)                 │ ETH  │
└──────┘                                       └──────┘
  │                                              ▲
  ▼  物理层传输                                  │

二、数据封装与解封装

2.1 封装过程

应用层数据(Payload)
    │
    ▼  + TCP Header(20-60 bytes)
传输层段(Segment)
    │
    ▼  + IP Header(20 bytes)
网络层包(Packet)
    │
    ▼  + ETH Header(14 bytes) + ETH Trailer(4 bytes)
链路层帧(Frame)
    │
    ▼  转化为比特流发送
物理层

2.2 各层 PDU(Protocol Data Unit)

层次名称标识说明
应用层Data / Message无HTTP 请求/响应体
传输层SegmentPortTCP 段/UDP 数据报
网络层PacketIP 地址IP 数据报
链路层FrameMAC 地址以太网帧
物理层Bit电信号/光信号比特流

2.3 Wireshark 实际观察

Frame 42: 1514 bytes on wire          ← 链路层帧
Ethernet II, Src: aa:bb:cc:dd:ee:ff   ← 以太网头部
Internet Protocol Version 4             ← IP 头部
Transmission Control Protocol           ← TCP 头部
Hypertext Transfer Protocol             ← HTTP 数据

三、IP 协议核心

3.1 IPv4 报文头部

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

3.2 子网划分

// Java 中计算子网
public class SubnetUtils {
    
    public static void main(String[] args) {
        // CIDR 表示法:192.168.1.0/24
        // 网络地址:192.168.1.0
        // 子网掩码:255.255.255.0(前 24 位为 1)
        // 可用主机数:2^(32-24) - 2 = 254
        
        int prefix = 24;
        int hostBits = 32 - prefix;
        int hostCount = (1 << hostBits) - 2;  // 减去网络地址和广播地址
        System.out.println("可用主机数: " + hostCount);  // 254
        
        // 判断 IP 是否在同一子网
        String ip1 = "192.168.1.100";
        String ip2 = "192.168.1.200";
        String mask = "255.255.255.0";
        
        int network1 = ipToInt(ip1) & ipToInt(mask);
        int network2 = ipToInt(ip2) & ipToInt(mask);
        System.out.println("同子网: " + (network1 == network2));
    }
    
    static int ipToInt(String ip) {
        String[] parts = ip.split("\\.");
        return (Integer.parseInt(parts[0]) << 24)
             | (Integer.parseInt(parts[1]) << 16)
             | (Integer.parseInt(parts[2]) << 8)
             | Integer.parseInt(parts[3]);
    }
}

3.3 IPv6 特点

特性IPv4IPv6
地址长度32 位128 位
地址数量约 43 亿3.4 × 10^38
头部长度可变(20-60 bytes)固定(40 bytes)
安全性IPsec 可选IPsec 内置
表示格式点分十进制冒号分隔十六进制
示例 IPv6 地址:
完整:2001:0db8:85a3:0000:0000:8a2e:0370:7334
压缩:2001:db8:85a3::8a2e:370:7334

四、ARP 协议

4.1 地址解析过程

主机 A(192.168.1.10)需要发送数据给主机 B(192.168.1.20)

1. A 检查 ARP 缓存,无 B 的 MAC 地址
2. A 发送 ARP 请求(广播):
   "谁的 IP 是 192.168.1.20?告诉 192.168.1.10(MAC: aa:bb:cc:dd:ee:ff)"
3. 所有主机收到请求,只有 B 响应(单播):
   "我是 192.168.1.20,MAC 是 11:22:33:44:55:66"
4. A 将 B 的 IP-MAC 映射存入 ARP 缓存
5. A 发送数据帧给 B

4.2 Java ARP 操作

// 通过 InetAddress 获取 MAC 地址(平台依赖)
InetAddress addr = InetAddress.getByName("192.168.1.1");
NetworkInterface ni = NetworkInterface.getByInetAddress(addr);
byte[] mac = ni.getHardwareAddress();

// 使用 ProcessBuilder 执行 arp 命令(Linux/macOS)
Process process = new ProcessBuilder("arp", "-a").start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}

五、ICMP 与 Ping

// Java 中使用 ProcessBuilder 执行 ping
public class PingTest {
    
    public static boolean ping(String host, int timeoutMs) throws Exception {
        ProcessBuilder pb = new ProcessBuilder("ping", "-c", "1", "-W", String.valueOf(timeoutMs / 1000), host);
        Process process = pb.start();
        
        boolean finished = process.waitFor(timeoutMs + 1000, TimeUnit.MILLISECONDS);
        if (!finished) {
            process.destroyForcibly();
            return false;
        }
        
        return process.exitValue() == 0;
    }
    
    // 使用 isReachable(基于 ICMP 或 TCP echo)
    public static boolean isReachable(String host, int timeoutMs) throws Exception {
        return InetAddress.getByName(host).isReachable(timeoutMs);
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println(ping("baidu.com", 3000));
        System.out.println(isReachable("baidu.com", 3000));
    }
}

六、MTU 与分片

6.1 MTU(Maximum Transmission Unit)

接口类型默认 MTU
以太网1500 bytes
PPPoE1492 bytes
IEEE 802.112304 bytes
回环接口65536 bytes
Jumbo Frame9000 bytes

6.2 路径 MTU 发现

发送方                                    接收方
  │ ─── 发送 DF=1(Don't Fragment)的包 ───→ │
  │                                         │
  │ ←── 收到 ICMP "Fragmentation Needed" ───│  MTU 只有 1400
  │                                         │
  │ ─── 发送 1400 bytes 的包 ─────────────→ │
  │                                         │
// Java Socket 设置 MTU
Socket socket = new Socket();
socket.setReceiveBufferSize(65536);
socket.setSendBufferSize(65536);

// NIO SocketChannel
SocketChannel channel = SocketChannel.open();
channel.socket().setReceiveBufferSize(65536);

七、TCP 连接管理:三次握手与四次挥手

TCP 是面向连接的协议,连接的建立和关闭有严格的状态机。

7.1 三次握手(建立连接)

客户端                                              服务端
  │ ─────── SYN=1, seq=x ───────────────► │  CLOSED → LISTEN
  │                                       │
  │ ◄── SYN=1, ACK=1, seq=y, ack=x+1 ──── │  LISTEN → SYN_RCVD
  │                                       │
  │ ─────── ACK=1, seq=x+1, ack=y+1 ────► │  SYN_RCVD → ESTABLISHED
  │                                       │
  ESTABLISHED                             │

为什么是三次而不是两次?

  • 两次握手无法防止历史重复连接初始化(已失效的 SYN 包延迟到达)。
  • 三次握手让双方都能确认对方具备收发能力(全双工)。

7.2 四次挥手(关闭连接)

客户端                                              服务端
  │ ─────── FIN=1, seq=u ───────────────► │  ESTABLISHED → FIN_WAIT_1
  │                                       │
  │ ◄──────── ACK=1, ack=u+1 ──────────── │  CLOSE_WAIT
  │                                       │  (服务端继续发未发完的数据)
  │ ◄──────── FIN=1, seq=w ───────────── │  LAST_ACK
  │                                       │
  │ ─────── ACK=1, ack=w+1 ─────────────► │  CLOSED
  │                                       │
  TIME_WAIT (2×MSL)                       │
  CLOSED                                  │

TIME_WAIT 状态持续 2 倍最大段生存期(MSL,通常 60 秒),目的是:

  1. 确保最后一个 ACK 能被对方收到(丢失后对方重发 FIN 仍能响应)
  2. 让网络中延迟的旧数据包完全消散,避免影响新连接

大量 TIME_WAIT Socket 的调优:

# 允许端口复用(开发/测试环境)
sysctl net.ipv4.tcp_tw_reuse=1
# 快速回收 TIME_WAIT(Linux 4.12+ 已废弃 tcp_tw_recycle)
# 更推荐方案:连接池化 + 长连接

八、TCP 可靠传输:滑动窗口与拥塞控制

8.1 滑动窗口

TCP 通过滑动窗口实现流量控制(Flow Control),防止发送方淹没接收方:

发送方窗口:[已发送并确认] [已发送未确认] [可发送] [不可发送]
                          ↑wnd        ↑rwnd

接收方通过 TCP Header 中的 Window 字段通告剩余缓冲区大小

当接收方缓冲区满时,通告 rwnd=0,发送方停止发送;接收方处理完数据后发送窗口更新报文。

8.2 拥塞控制

拥塞控制防止发送方淹没网络(与流量控制的目标不同):

阶段算法行为
启动慢开始(Slow Start)cwnd 从 1 MSS 指数增长,每 RTT 翻倍
过渡拥塞避免(Congestion Avoidance)cwnd 线性增长,每 RTT +1 MSS
丢包快速重传 + 快速恢复收到 3 个重复 ACK 后减半 cwnd
超时超时重传cwnd 直接降到 1,重新慢开始

现代 Linux 默认使用 CUBIC 拥塞控制算法(高带宽延迟网络的 BBR 正在普及):

# 查看当前拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# 切换为 BBR(需要内核支持)
sysctl net.ipv4.tcp_congestion_control=bbr

九、DNS 解析过程

当你访问 www.example.com 时,域名的解析过程:

浏览器缓存 → 操作系统缓存 (/etc/hosts) → 本地 DNS 服务器 (递归)
  │
  └── 本地 DNS 向根服务器查询 .com NS
      └── 向 .com TLD 服务器查询 example.com NS
          └── 向 example.com 权威服务器查询 www A 记录
              └── 返回 IP 地址
记录类型用途
AIPv4 地址
AAAAIPv6 地址
CNAME别名指向另一个域名
MX邮件服务器
TXT文本记录(SPF、DKIM、验证)
NS域名服务器

DNS 优化手段:

  • DNS 预解析:<link rel="dns-prefetch" href="//cdn.example.com">
  • HTTPDNS:绕过运营商 Local DNS,App 直接调用 HTTP API 解析
  • EDNS Client Subnet:让 CDN 根据用户真实 IP 调度最优节点

十、TLS 握手与 HTTPS 安全

HTTPS = HTTP + TLS(前身 SSL)。TLS 1.2 握手需要 2-RTT,TLS 1.3 优化到 1-RTT(甚至 0-RTT 会话恢复)。

10.1 TLS 1.2 握手流程

客户端                                          服务端
  │ ── ClientHello (支持的加密套件、随机数) ──► │
  │ ◄─ ServerHello + Certificate + ServerKeyExchange ── │
  │ ── ClientKeyExchange + ChangeCipherSpec ──► │
  │ ◄─ ChangeCipherSpec (Finished) ───────────── │
  │ ── Finished ───────────────────────────────► │
  │                                               │
  加密通信开始(对称密钥协商完成)

10.2 TLS 1.3 改进

维度TLS 1.2TLS 1.3
RTT21 (或 0-RTT 恢复)
支持的加密套件多(含不安全算法)少(仅 AEAD)
握手消息明文传输部分参数除 ClientHello 外全部加密
前向安全取决于密钥交换方式强制前向安全
# Nginx 启用 TLS 1.3
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off;

十一、HTTP/2 与 HTTP/3 演进

特性HTTP/1.1HTTP/2HTTP/3
传输层TCPTCPQUIC (基于 UDP)
多路复用无(队头阻塞)有(帧级别)有(Stream 独立)
头部压缩无HPACKQPACK
服务器推送无有有
连接建立TCP + TLS = 2-3 RTTTCP + TLS = 2-3 RTT0-1 RTT
队头阻塞应用层解决彻底解决(UDP 无连接级阻塞)

HTTP/2 的多路复用解决了浏览器对同一域名并发连接数的限制(HTTP/1.1 通常为 6~8 条),但仍受 TCP 层队头阻塞影响——一个 TCP 包丢失会阻塞所有 Stream。

HTTP/3 使用 QUIC(基于 UDP 实现可靠传输),每个 Stream 独立的滑动窗口,一个 Stream 丢包不影响其他 Stream。但由于 UDP 在企业防火墙/运营商可能被限制,HTTP/3 的普及仍在进行中。


十二、常见网络故障排查速查

症状可能原因排查命令
无法访问网站DNS 解析失败dig +trace / nslookup
连接超时防火墙/路由不通traceroute / mtr
连接被拒绝目标端口未监听telnet host port / nc -zv
高延迟路由绕行/拥塞mtr --report
间歇性丢包MTU 不一致/物理层问题ping -s 1472 测试大包
HTTPS 证书错误证书过期/SNI 不匹配openssl s_client -connect
本地开发正常、远程异常CORS/防火墙/SNATcurl -v 对比请求头
# 完整诊断示例:排查 api.example.com 访问异常
dig api.example.com                   # DNS 是否正常
ping -c 4 api.example.com             # 网络层是否可达
traceroute api.example.com            # 路由路径
nc -zv api.example.com 443            # 端口是否开放
curl -v -o /dev/null https://api.example.com/health  # HTTP/TLS 层
ss -tin state established '( dport = :443 )'  # 本地 TCP 状态

七、常用网络工具

工具用途示例
ping测试连通性ping -c 4 baidu.com
traceroute路由追踪traceroute baidu.com
netstat查看连接netstat -tlnp
ss替代 netstatss -tlnp
tcpdump抓包tcpdump -i eth0 port 80
Wireshark图形化分析分析 pcap 文件
telnet测试端口telnet baidu.com 80
nc网络瑞士军刀nc -zv baidu.com 80
curlHTTP 请求curl -v https://api.example.com
nmap端口扫描nmap -sS 192.168.1.1

八、总结

层次核心协议关键概念
应用层HTTP/HTTPS、gRPC、DNS请求/响应、RPC
传输层TCP、UDP端口、连接、可靠性
网络层IP、ICMP、ARP地址、路由、分片
链路层Ethernet、Wi-FiMAC、帧、交换机

协议栈的分层设计体现了软件工程中"关注点分离"的核心思想。每一层专注于自己的职责,通过标准化的接口与上下层交互,使得整个网络通信系统既灵活又可靠。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. 46. 排队论与容量估算:利特尔法则与尾延迟
  2. MPTCP 与多路径传输
  3. 流量整形与 TC/qdisc