蓝图 · 250 字 · 1 分钟阅读

TCP/IP:连通一切的协议

两位工程师在酒店大堂,一个不可能的问题:让任何网络能与任何其他网络通话。Cerf 与 Kahn 1974 年的论文,如何成为互联网的地基。

#TL;DR

到 1973 年,ARPANET 已经能用——但它是一个围墙花园。它的协议 NCP 假设只有一个从不丢包、彼此信任的网络。现实世界里有几十张互不兼容的网,没有一张能互相对话。Vint Cerf 和 Bob Kahn 在一间酒店大堂里解决了这个问题:设计一个把每张网络都当成不可靠的协议,把所有难做的事都丢到两端去做,而不是放到中间。TCP/IP 成了互联网的通用语。1983 年 1 月 1 日,ARPANET 扳下了一个开关,现代互联网诞生了。

#Kahn 带到斯坦福的问题

1973 年春天,Bob Kahn 走进 Vint Cerf 在斯坦福的办公室,告诉他一个”灾难性的问题”。

ARPANET 成功了,但它只是一张网。美军现在还有卫星网、移动分组无线网,加上 ARPANET——这几张网彼此之间不能交换数据。它们用的分组大小不同、地址方案不同、错误处理规则不同。用网关把它们接起来只会让问题更糟:每个网关都得懂每张网的方言。

Kahn 想这件事想了好几个月,列出了四个要求:

  1. 每张网内部不应该被改
  2. 如果一个包没到,应该重传
  3. 网关不应该保留状态——某个挂了,包可以重路由
  4. 不应该有全局控制

他有约束,但没有解法。他需要一个理论家。

Cerf 就是那个理论家。

#Palo Alto 的酒店大堂

接下来的几周里,两个人在哪儿都能碰头——办公室、咖啡馆,最出活的地方是 Palo Alto 的 Rickey’s Hyatt House 大堂。Cerf 在餐巾纸上画草图,Kahn 争论各种故障模式。从中浮现出来的核心洞见,简洁得让人意外:

网络是笨的,端点是聪明的。

他们不打算建一张可靠的网——那需要中间每台设备都配合——而是建一张不可靠的网,让端点来负责从这种不可靠里恢复过来。网关(后来叫路由器)只做一件事:转发数据包。它们不保存状态、不验证送达、除了看一眼目的地址把包转走,什么都不做。

这就是后来所谓的端到端原则:把智能放在边缘,而不是核心。它是计算史上影响最深远的设计决策之一。

1974 年 5 月,Cerf 和 Kahn 在 IEEE Transactions on Communications 上发表了 “A Protocol for Packet Network Intercommunication”。它描述了一个单一协议——TCP——来处理一切。

#拆分:TCP 变成 TCP/IP

原始的 TCP 想干的事太多了。它同时处理寻址(包去哪儿)、路由(怎么到)、可靠性(没到怎么办)。这对研究论文来说没问题,但在实现上是噩梦。

Jon Postel——后来负责互联网命名和编号的那位 RFC 编辑——唱了反调。他说这其实是两个完全不同的问题:

  • 路由是网络层的事。每个包都需要地址和路径。
  • 可靠性是传输层的事。只有一部分应用需要保证送达。

直播视频可以容忍丢包;邮件传输不行。把可靠性塞进网络层意味着视频不管想不想要,都得背上保证送达的开销。

Postel 的论点赢了。1978 年,TCP 被拆成两个:

  • IP(Internet Protocol)—— 负责寻址和路由。发了就不管。
  • TCP(Transmission Control Protocol)—— 跑在 IP 之上,加上可靠性。

那些追求速度胜于可靠性的应用,可以直接在 IP 上用 UDP(User Datagram Protocol)。结果形成了一个沙漏:

  HTTP  FTP  SMTP  DNS  ...    ← 许多应用协议
       ╲  │  │  ╱
        ╲ │  │ ╱
    ┌────────────────┐
    │   TCP  │  UDP  │         ← 传输层
    ├────────────────┤
    │       IP       │         ← 细腰
    └────────────────┘
        ╱ │  │ ╲
       ╱  │  │  ╲
  Wi-Fi  Ethernet  4G  ...    ← 许多链路层技术

IP 就是那个细腰。下面每种链路技术、上面每种应用,都讲 IP。这就是为什么你能在 Wi-Fi、以太网或蜂窝网络上收邮件——网络不在乎,应用也不在乎,它们都讲 IP。

#IP:每台机器都有一个地址

IPv4 给互联网上每台设备一个 32 位地址——四个 0 到 255 的数字、点分隔开。192.168.1.1。你家路由器有一个,托管这个页面的服务器也有一个。1974 年,32 位看上去绰绰有余:一颗 40 亿人口的星球,配 43 亿个地址。

(并不够。不过那是 1990 年代才要操心的事。)

IP 路由工作方式像邮政。每个包带着目的地址。路由器不知道完整路径——它们只知道下一跳。每台路由器根据自己的路由表把包朝目的地推一下,包就这样一跳一跳穿越互联网。

你 → 家用路由器 → ISP 路由器 → ... → 目的地
         "发给我上游"     "发给下一个 AS"

路由器通过 BGP(Border Gateway Protocol)之类的协议互相合作,构建并共享这些路由表。它们不需要一张整个互联网的地图——它们只需要知道下一步。这就是去中心化的实践:没有哪台中央路由器知道一切,但包仍然能自己找到路。

#TCP:把不可靠的网络变可靠

IP 尽力投递分组。它们可能乱序到达、被复制、甚至完全消失。TCP 的任务是把这一切从应用那里掩盖掉。

在两台机器交换数据之前,TCP 先做三次握手建立连接:

Client                    Server
  │                          │
  │──── SYN ────────────────>│   "我想连(seq=x)"
  │                          │
  │<─── SYN-ACK ─────────────│   "好,我准备好了(seq=y, ack=x+1)"
  │                          │
  │──── ACK ────────────────>│   "很好,开始(ack=y+1)"
  │                          │
  │═══════ 数据开始流动 ═════│

连接建立后,TCP 给自己发出的每一个字节编号。接收端为它收到的每一个字节发回确认(ACK)。如果发送端没在规定时间内收到 ACK,就重传。接收端会把乱序的包重新排序,重复的包会被丢掉。

import socket

# TCP 连接——操作系统处理 SYN/SYN-ACK/ACK、重传、排序
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(("example.com", 80))
    s.sendall(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
    response = s.recv(4096)

# UDP——裸 IP,没有握手,没有保证
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.sendto(b"ping", ("8.8.8.8", 53))
    data, addr = s.recvfrom(512)

TCP 还做流控(不要发得比接收端能处理的还快)和拥塞控制(不要发得比网络能扛得住的还快)。这些算法——Nagle、慢启动、CUBIC——今天仍在被调。现代的 HTTP/3 用 QUIC 替换了 TCP,部分原因就是要绕开 TCP 的队头阻塞。不过那是第 5 纪元的事了。

#旗日:1983 年 1 月 1 日

1974 年论文之后的八年里,TCP/IP 和 NCP 共存着。实现 TCP/IP 是可选的。然后美国国防部把它定成了强制——并定了一个截止日期。

1983 年 1 月 1 日。 ARPANET 上每台机器都要同时从 NCP 切到 TCP/IP。没有分阶段上线,没有向后兼容期。如果到午夜你的机器还不会讲 TCP/IP,它就被踢出网。

管理员们叫它 “旗日”(flag day)。有人觉得这样鲁莽,但它管用。1 月 1 日,ARPANET 醒来时已经在讲一门新语言——就是今天互联网仍在讲的那门。

1983 年发布的 4.2BSD Unix 把 TCP/IP 直接做进了操作系统。突然,任何 Unix 机器都能连上网。互联网就此从一个政府研究项目,开始变成基础设施。

#TCP/IP 做对了什么

1973 年那场酒店大堂里做出的设计决定,撑了五十年:

  • 网络笨,端点聪明 — 因为路由器不需要理解它转发的流量,互联网才能扩展到几十亿台设备。
  • 分层 — Wi-Fi 替代以太网,IPv6 慢慢取代 IPv4,QUIC 在挑战 TCP——这些变化都没让应用出事。
  • 尽力而为的投递 — 在 IP 这一层坦然接受”不可靠”,而不是装作网络可靠,让整个系统更诚实,也更稳健。
  • 开放标准 — 任何人都能实现 TCP/IP。IBM 不拥有它,AT&T 也不拥有它。手里有 RFC 的人都能搭一个合规的协议栈。

它没解决的一件事:名字。192.0.2.1 这样的 IP 地址,不是人用来导航的方式。你没法输入 google.com 并得到任何意义——每台机器都要自己维护一个 hosts.txt 文件,把名字映射到地址,而那个文件由斯坦福的一个管理员维护。到 1983 年,这个文件已经有几千条,每周更新两次。这扩不下去。

答案是 DNS——Domain Name System,一个分布式、层级式的数据库,把人类可读的名字翻译成 IP 地址。那是下一站。