计算机网络要解决的问题极其复杂:两台机器如何建立连接? 数据传输出错了怎么办? 如何找到对方的物理地址? 如果把所有功能放在一个模块里,代码会变成难以维护的「一团乱麻」。
TCP/IP 协议栈通常划分为 5 层(也有 4 层或 7 层 OSI 模型的划分),每一层有明确的职责:
封装 (Encapsulation):发送数据时,每一层都给数据添加自己的头部(Header),包含控制信息(比如源地址、目标地址、序号等)。
解封装 (Decapsulation):接收数据时,每一层读取自己的头部,处理后去掉头部,把数据交给上一层。
| 层 | 功能 | 协议示例 |
|---|---|---|
| 应用层 | 为应用程序提供网络服务 | HTTP, HTTPS, DNS, FTP, SMTP |
| 传输层 | 提供端到端的数据传输服务 | TCP, UDP, QUIC |
| 网络层 | 负责数据包的路由和寻址 | IP, ICMP, ARP |
| 链路层 | 负责相邻节点的数据传输 | Ethernet, WiFi, PPP |
| 物理层 | 传输比特流 | 光缆、双绞线、无线电波 |
互联网上有数十亿台设备,每台设备都需要一个唯一的标识符,这就是 IP 地址。但只有地址还不够:如何找到从你的手机到服务器的最佳路径? 这就是 路由 要解决的问题。
IPv4 地址是 32 位的,通常写成 4 个十进制数(例如 192.168.1.1)。为了高效管理,IP 地址被划分为网络号和主机号两部分,通过子网掩码来区分。
CIDR (无类别域间路由):传统的 A/B/C 类地址划分方式太浪费,CIDR 用前缀长度表示子网(例如 /24 表示前 24 位是网络号),大大提高了地址利用率。
# 查看路由表
$ ip route
default via 192.168.1.1 dev wlan0 proto dhcp
192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.100
# 抓包查看 IP 头部
$ tcpdump -i wlan0 -n 'ip'
10:00:00.000000 IP 192.168.1.100.12345 > 8.8.8.8.80: Flags [S], seq 123456, win 64240, length 0
路由表:每台路由器都维护一张路由表,包含「目标网络 → 下一跳」的映射。最长前缀匹配 (Longest Prefix Match) 是路由查找的核心规则。
IP 协议只负责「尽力而为」地把数据包送出去:包可能丢失、可能乱序到达、可能重复。但应用程序(比如下载文件、网页浏览)需要可靠的传输服务——TCP 就是为了解决这个问题的。
三次握手:建立 TCP 连接需要三次握手,主要目的是同步序列号(ISN),让双方知道对方的起始序列号,为后续的可靠传输奠定基础。
滑动窗口:TCP 用滑动窗口实现流量控制和可靠传输。发送方可以连续发送多个包而不必等每个包的确认,大大提高了传输效率。
拥塞控制:TCP 必须探测网络的承载能力,避免发送太多数据导致网络过载。核心算法包括:慢启动、拥塞避免、快速重传、快速恢复。
# 抓取 TCP 三次握手
$ tcpdump -i wlan0 -n 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
10:00:00.000000 IP 192.168.1.100.12345 > 8.8.8.8.80: Flags [S], seq 123456, win 64240, length 0
10:00:00.001000 IP 8.8.8.8.80 > 192.168.1.100.12345: Flags [S.], seq 654321, ack 123457, win 65535, length 0
10:00:00.001500 IP 192.168.1.100.12345 > 8.8.8.8.80: Flags [.], ack 654322, win 64240, length 0
TCP 提供了可靠传输,但也带来了开销:需要建立连接(三次握手)、需要维护状态、需要重传、队头阻塞。有些应用不需要这些:实时视频通话—— 丢包没关系,只要延迟低;DNS 查询—— 小查询,简单重传就够了。
UDP 头部非常简单,只有 8 个字节:源端口、目标端口、长度、校验和。相比之下,TCP 头部至少 20 字节。
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 顺序 | 保证顺序 | 不保证 |
| 流控 | 有 | 无 |
| 拥塞控制 | 有 | 无 |
| 头部大小 | 20~60 字节 | 8 字节 |
| 适用场景 | 文件传输、网页浏览 | 视频通话、DNS、游戏 |
QUIC: 不是所有应用都想在 UDP 之上重新实现可靠传输。QUIC (Quick UDP Internet Connection) 在 UDP 之上提供了像 TCP 一样的可靠传输和拥塞控制,但又解决了 TCP 的队头阻塞问题,同时集成了 TLS 加密。
# 抓取 DNS 查询 (UDP 53 端口)
$ tcpdump -i wlan0 -n 'udp port 53'
10:00:00.000000 IP 192.168.1.100.12345 > 8.8.8.8.53: 12345+ A? www.google.com. (32)
10:00:00.001000 IP 8.8.8.8.53 > 192.168.1.100.12345: 12345 1/0/0 A 142.250.185.100 (48)
HTTP (HyperText Transfer Protocol) 是 Web 的基础协议:浏览器(客户端)发送请求给服务器,服务器返回响应。但 HTTP 是明文的,任何人都可以窃听或篡改,所以需要 HTTPS——HTTP over TLS,在 HTTP 之下加了一层加密。
HTTP 请求:包含方法(GET、POST 等)、URL、HTTP 版本、请求头、请求体。
HTTP 响应:包含 HTTP 版本、状态码(200 OK、404 Not Found 等)、响应头、响应体。
# 发送 HTTP GET 请求,显示详细信息
$ curl -v http://example.com
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.64.1
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=UTF-8
< Content-Length: 1256
<!doctype html>
<html>...</html>
HTTPS (TLS):在 HTTP 之下加了 TLS 层,提供:加密(防止窃听)、认证(确认服务器身份)、完整性(防止篡改)。
HTTP/2:主要改进:多路复用(多个请求共享一个 TCP 连接,无队头阻塞)、二进制分帧、头部压缩、服务器推送。
HTTP 缓存:通过 Cache-Control、ETag、Last-Modified 等头部控制缓存,大大减少重复请求,提高性能。
IP 地址是给机器看的(比如 142.250.185.100),人很难记住。DNS (Domain Name System) 就是互联网的「电话簿」:把人类可读的域名(比如 www.google.com)转换成机器可读的 IP 地址。
DNS 层次结构:域名从右往左读,用点分隔。例如 www.google.com:com 是顶级域 (TLD),google 是二级域,www 是主机名。
递归查询:客户端问本地 DNS「帮我找到 www.google.com 的 IP」,本地 DNS 会负责一层层问下去,最后把结果返回给客户端。
迭代查询:本地 DNS 问根服务器「.com 的服务器在哪?」,根服务器返回 .com 服务器的地址;本地 DNS 再问 .com 服务器「google.com 的服务器在哪?」,如此迭代直到找到答案。
缓存:DNS 查询结果会被缓存一段时间(TTL,Time To Live),避免每次都从头查询,大大提高效率。
# 查询 www.google.com 的 A 记录
$ dig www.google.com
;>DiG 9.10.6<<>> www.google.com
;;ANSWER SECTION:
www.google.com. 300 IN A 142.250.185.100
# 跟踪整个查询过程
$ dig +trace www.google.com