
本文是对 A short (and mostly wrong) history of computer networking 的整理与翻译。内容结构概览为什么要先讲网络史实现 ping 之前需要先知道计算机之间如何通信。为什么要连接计算机从早期大型机、打孔卡、分时系统、终端讲起。远程终端不等于计算机网络终端只是访问大型机不是多台计算机互联。ARPANET、Telnet 与 TCP/IP现代互联网的雏形来自数据交换和远程控制需求。两台计算机如何通信电缆、电信号、比特、模拟信号、噪声与时钟同步。多台计算机为什么不能全连接网卡数量、线缆复杂度和扩展性都会爆炸。总线与以太网思想共享介质、数据报、MAC 地址、冲突检测、随机等待、校验和。为什么还需要 IPMAC 地址适合局域网识别IP 地址适合跨网络路由。DNS 与 DHCP域名解决记不住 IP 的问题DHCP 解决手动配置 IP 的问题。回到 pingping 是理解网络协议栈的入口不只是一个测试连通性的命令。如果想自己实现一个ping很多人第一反应是去查 ICMP 协议格式然后直接写代码构造数据包。但如果一开始就这么做很容易只学到“怎么填字段”却没有真正理解这些字段为什么存在也不知道一个看似简单的ping命令背后到底经历了哪些网络层次。ping表面上很简单输入一个 IP 地址程序发出请求目标机器如果能收到并返回响应屏幕上就显示延迟、丢包率等信息。但真正发生的事情远比这复杂。你的机器要知道目标地址是不是本地网络要不要交给网关如果要发到局域网内还要知道对方的 MAC 地址如果目标在公网还要经过一系列路由器转发如果你输入的是域名还要先通过 DNS 查出 IP如果机器刚接入网络自己的 IP、网关和 DNS 也可能是通过 DHCP 自动获得的。所以在自己实现ping之前先把计算机网络这件事讲明白是有必要的。网络不是一套凭空设计出来的复杂名词而是人们在解决一个又一个具体问题时逐步形成的系统。先有两台计算机之间传递数据的需求再有多台计算机共享通信介质的问题先有局域网内部寻址再有跨网络路由先有 IP 地址再有人类更容易记住的域名最后才有我们每天随手使用的ping、浏览器、SSH、Git、数据库连接和各种网络服务。一、计算机为什么需要联网今天我们几乎默认计算机一定要联网。手机、电脑、服务器、路由器、摄像头、智能家居设备都在网络里很多软件如果没有网络就近乎不可用。但在早期计算机并不是这样工作的。20 世纪 40 年代的计算机体积巨大、价格昂贵能负担得起计算机的机构通常也只有一台大型机器而且这台机器一次只能执行一个任务。以 ENIAC 这类早期计算机为例使用方式和今天完全不同。那时所谓“编程”往往意味着拨动开关、插拔电缆再通过打孔卡输入数据。输出结果也可以通过打印设备写回打孔卡。站在非常宽泛的角度看打孔卡也算一种让计算机和外部世界交换数据的方式但它显然还不是我们今天理解的计算机网络。真正的变化来自资源共享。早期计算机太贵不可能每个人都有一台所以人们开始考虑怎样让多人共享同一台大型计算机。分时系统就是这种思路下的产物。最开始系统可能只是把用户提交的任务排队执行一个任务结束后马上运行下一个任务。后来计算机可以把任务切成更小的时间片快速在多个任务之间切换。虽然硬件在某个瞬间仍然只做一件事但用户感受到的是多个任务似乎在同时运行这就是多任务的早期形态。用户和大型机交互时使用的是终端。终端通常有键盘和显示器但它本身不是完整的计算机不负责真正的计算。终端更像是大型机的远程输入输出设备用户通过键盘输入命令命令传到大型机执行结果再显示在终端屏幕上。某些高级分时系统已经允许终端远程接入这在形式上很接近今天的远程访问但它仍然不是多台独立计算机之间的通信因为终端本身并不承担计算任务。与此同时另一条路线也在发展让不同的计算机互相连接。这里的目标不只是让人远程使用一台大型机而是让多台计算机之间交换数据。不同机器可以共享信息各自执行擅长的任务再组合起来解决更复杂的问题。ARPANET 就属于这个方向早期计划在 1967 年左右已经出现。它的重点是数据交换而不是简单地把某台机器的操作界面搬到远处。后来远程控制和数据交换这两类需求逐渐融合。Telnet 这样的远程登录协议出现后人们可以通过网络控制另一台计算机。Telnet 最早运行在 ARPANET 的 NCP 之上后来在 1983 年迁移到 TCP/IP。这次切换通常被视为现代互联网形成过程中的重要节点。到这里计算机网络的核心动机已经很清楚昂贵的计算能力需要共享集中保存的信息需要访问不同地区的人和组织需要高效通信而技术本身也会推动更多连接方式出现。二、两台计算机怎样连接先从最简单的情况开始只有两台计算机。如果只想让两台机器通信最直接的办法就是用一根线把它们连起来。计算机是电子设备电缆可以传输电信号只要线足够短、导电性足够好、信号损耗可以忽略就可以先假设一台计算机能把电信号传到另一台计算机。但计算机内部处理的是比特也就是 0 和 1电信号本身不是天然的 0 和 1它是连续变化的模拟量。因此还需要一套编码方式把比特转换成电信号再在接收端把电信号还原成比特。比如可以约定每隔 100 纳秒检测一次电压变化通过信号穿过 0V 的方向来判断发送的是 0 还是 1。这个例子只是为了说明思路真实世界中的信号编码、同步、抗干扰会复杂得多。只要电缆足够长问题就会变多。信号在传输中会衰减周围电气设备会带来噪声发送端和接收端的时钟也不可能天然完全一致。要想稳定传输数据就必须处理信号质量、同步、错误检测等问题。这些属于物理层和电气工程领域细节可以非常复杂但在理解网络整体结构时可以先做一个抽象假设我们已经有办法在两台计算机之间可靠地发送 0 和 1。有了这个抽象后面的网络协议就可以搭建起来。协议的本质不是从零创造通信能力而是在某种已有传输能力之上规定数据格式、地址、顺序、错误处理和转发规则。只要最底层能把比特从一端送到另一端就可以逐步构建出越来越复杂的通信系统。三、三台、六台、更多台计算机怎么办两台计算机用一根线连接很容易理解。如果有三台计算机最直接的办法是每两台之间都拉一根线。这样 A 和 B 之间有一条链路A 和 C 之间有一条链路B 和 C 之间也有一条链路。每台机器可以配多个网络接口每个接口连接不同机器。收到某个接口传来的数据时就能判断数据来自哪台机器。三台机器时这种结构还勉强可以接受。但机器数量一多线缆和网卡数量就会迅速爆炸。六台计算机之间如果要两两直接连接每台机器就需要五个网络接口整个环境里会出现大量交叉线缆。逻辑图看起来已经很混乱真正放到建筑物里布线只会更麻烦。新增一台机器也不是简单插上一根线而是要和已有机器分别连接扩展成本很高。这说明全连接结构不适合规模化。它在数学上很好理解但在工程上很快就变得昂贵、复杂、难维护。要想连接更多计算机必须换一种思路不要让每两台机器都拥有独立线路而是让多台机器共享某种通信介质。这就引出了总线结构。总线可以理解成一根主干电缆所有计算机都接到这根电缆上。每台计算机只需要一个网络接口通过一根线接入这条公共总线。这样一来线缆数量大幅减少每台机器也不需要为其他每台机器单独准备网卡。但总线虽然解决了布线问题却马上带来了新的问题。所有机器都在同一根线上通信大家会互相听到对方的信号如果没有地址就不知道数据是发给谁的如果没有规则多台机器还可能同时发送数据导致信号互相干扰。连接方式变简单了通信规则反而必须变复杂。四、共享一根线之后必须有数据片段和地址在两台计算机直接相连时一台机器可以一直发送因为这条链路只属于它们两个。接收方天然知道数据来自谁也知道数据是发给自己的。但在总线结构里所有机器共享同一根通信介质任何一台机器发送的数据都会出现在总线上其他机器都有机会听到。这时通信不能再是一段无限持续的“讲话”而要被切成一个个片段。每个片段可以理解成一次较小的数据传输也就是后面各种网络协议中常见的帧、包、数据报等概念。共享介质在任意时刻只能承载有限通信但如果把多个对话切成许多小片段并在时间上快速交替发送就能制造出多组通信似乎同时进行的效果。这和分时系统有类似思想用快速切换来共享唯一资源。解决“谁是谁”的问题则需要地址。更准确地说需要给每个网络接口分配一个唯一地址而不是简单给每台计算机分配一个地址。因为一台计算机可以有多个网络接口每个接口可能连接不同网络。这个地址就是 MAC 地址的思想。MAC 是 Media Access Control可以理解为在共享通信介质上识别网络接口、控制访问的一种地址。如果 MAC 地址用 6 个字节表示就能提供大约 281 万亿个不同地址。这个空间足够大可以给非常多的网络接口分配唯一标识。有了 MAC 地址之后每个数据片段里就可以带上源 MAC 地址和目标 MAC 地址。源地址说明这段数据是谁发出的目标地址说明这段数据希望交给谁。总线上的每台机器都能看到数据但只有目标 MAC 匹配自己的机器才需要处理其他机器可以忽略。这就从根本上改变了共享介质的通信方式。以前一根线只连接两台机器不需要在数据里写明身份现在一根线连接多台机器身份必须成为数据格式的一部分。地址不是额外装饰而是共享网络能正常工作的前提。五、大家同时发送怎么办共享总线还要解决冲突问题。如果两台机器同时往总线上发送电信号信号会互相叠加、干扰接收方就无法正确还原数据。数据片段会损坏通信也就失败了。既然所有机器都连在同一根总线上它们也能听到总线上是否有其他机器正在发送信号。一个自然的规则是发送之前先监听如果总线上有信号就先等待等总线安静下来再开始发送。这个规则解决了一部分问题但还不够。如果多台机器都在等待总线空闲它们可能同时检测到“现在安静了”然后一起开始发送于是又发生冲突。为了降低这种情况可以让机器在发生冲突后等待一段随机时间再重新尝试发送。随机等待可以避免所有机器总是在同一时刻重试从而减少反复冲突的概率。即使有监听和随机等待传输错误仍然不可避免。电气噪声、冲突、信号衰减都可能破坏数据。因此每个数据片段还可以附带校验和。接收方根据校验和判断数据是否损坏。如果校验失败就丢弃这段数据。至于是否重传、什么时候重传可以交给更高层协议处理。到这里一套局域网通信的基本模型已经出现底层负责把比特变成信号并发出去数据链路层负责把连续通信切成片段给片段加上源地址、目标地址和校验信息所有机器共享通信介质但通过监听、冲突处理和随机等待减少互相干扰。这套思想就是以太网的重要基础。以太网不仅是“把线插上”这么简单它同时包含物理层和数据链路层的设计。物理层关心怎样在线缆上传输比特数据链路层关心在共享介质中怎样识别设备、怎样组织数据、怎样发现错误、怎样处理冲突。现代以太网已经和早期共享总线形态有很大不同但理解总线模型有助于理解 MAC 地址和数据链路层为什么存在。六、局域网不能无限扩展总线结构和以太网思路解决了一个局部问题在一个相对有限的范围内多台计算机可以共享通信介质并互相识别。但它不能无限扩展。想象把整个欧洲都接到一根超长电缆上让所有计算机都处在同一个总线网络里这在工程上几乎不可行。首先线缆越长信号衰减和失真越严重。电信号通过金属导体传播时不可能完全保持原样距离越远噪声、衰减和延迟越明显。到了一定距离后接收端看到的信号可能已经很难和噪声区分。即使假设有一种理想导体完全没有能量损耗仍然绕不过传播速度限制。信号传播速度最多也只能接近光速而光速虽然很快却不是无限快。如果马德里到伦敦相距一千多公里信号从一端传到另一端也需要数毫秒。对于人来说几毫秒很短对于 100Mbit/s 这种传输速率来说几毫秒足够连续发送大量比特。这会破坏前面依赖“监听总线”的冲突检测策略。因为远处某台机器开始发送时信号还没有传到你这里你可能以为总线是空闲的于是也开始发送。等双方发现冲突时已经发出了大量数据。距离越远这种问题越严重。因此共享总线不适合构建跨城市、跨国家、跨洲的巨大网络。于是需要另一类设备来连接不同网络。最简单的想法是做一个有多个以太网端口的机器从某个端口收到数据后就把它原样转发到其他端口。这有点像早期集线器。它可以把多个局域网段连起来但如果只是无脑转发问题并没有根本解决。所有流量仍然会被广播到所有地方本地通信也会浪费远端网络资源冲突概率和等待时间也会继续上升。要连接更多计算机不能只靠“把所有东西转发给所有人”。网络设备必须能判断数据应该留在本地还是应该送到远方应该走哪个方向交给哪个下一跳。这就是路由问题。七、为什么有了 MAC 地址还需要 IP 地址MAC 地址可以标识网络接口但它不适合解决全球范围内的路由问题。MAC 地址本质上更接近硬件身份它的分配通常和厂商有关。例如某些 MAC 地址前缀属于某个厂商。这样的地址能区分设备却不天然表达“这个设备在哪个网络里”“这个数据应该往哪个方向走”。要跨越不同地区、不同网络传输数据需要一种更适合分组和层级转发的地址。比如可以规定某个地区、某个组织或某个网络使用某一段地址。巴黎网络里的机器使用一类地址马德里网络里的机器使用另一类地址。网络设备看到目标地址后可以根据地址前缀判断数据应该留在本地还是转发到另一个网络。这就是 IP 地址的意义。IP 地址不是取代 MAC 地址而是解决另一个层次的问题。MAC 地址负责局域网内的设备识别和下一跳交付IP 地址负责更大范围的寻址和路由。一个 IP 包在跨网络传输时目标 IP 通常保持不变但每一跳在局域网里交付时都需要使用当前链路上的 MAC 地址。如果只用 MAC 地址构建全球网络路由器就很难通过地址结构判断路径可能需要保存海量设备的具体位置这不具备可扩展性。IP 地址的层级结构让路由器不必知道全世界每台机器在哪里只需要根据路由表和地址前缀决定下一跳。这个分层思想是互联网能够扩展的关键。IPv4 就是这种思路下的协议。一个 IPv4 地址由 32 位组成通常写成四段十进制数字比如8.8.8.8、192.168.1.60、255.255.255.255。每段范围是 0 到 255。某些地址可以在公网中路由某些地址则属于私有网络只在局域网或内部网络中使用。通过 IP 地址和路由器世界各地的网络可以连接起来而不需要所有机器共享同一根物理总线。八、DNS人不应该记一堆数字地址有了以太网和 IP计算机之间已经可以在大范围内通信。但只靠 IP 地址使用网络并不方便。人类不擅长记大量数字地址也不愿意每次访问一个服务都输入一串数字。访问网站时我们更自然地输入域名比如example.com而不是某个具体 IP 地址。DNS 就是为了解决这个问题。DNS 通常展开为 Domain Name System也就是域名系统。它的核心作用是把人类可读的域名映射到机器可处理的 IP 地址。用户输入域名后系统通过 DNS 查询得到对应 IP然后再根据这个 IP 发起网络连接。DNS 的存在让互联网从“机器地址系统”变成了“人也能使用的系统”。用户不需要关心某个服务当前部署在哪台机器也不需要记住 IP 地址。服务提供方也可以调整背后的服务器地址只要 DNS 记录更新用户仍然使用同一个域名访问。当然DNS 自身也很复杂。真实世界里有递归解析、权威服务器、根服务器、缓存、TTL、负载均衡、多个记录类型等概念。但在理解ping之前只需要抓住它最基础的作用如果 ping 的目标是域名系统必须先把域名解析成 IP 地址后续网络层才能继续工作。九、DHCP刚接入网络时机器怎么获得 IPIP 地址还有一个现实问题每台机器的地址从哪来在服务器环境里可以手动配置 IP、子网掩码、网关和 DNS 服务器。但对普通用户来说手动配置网络太麻烦了。笔记本连上家里的 Wi-Fi、公司的网络或咖啡馆的热点时不可能每次都手动选择一个没有被占用的 IP再配置默认网关和 DNS。DHCP 就是为了解决自动配置问题。DHCP 是 Dynamic Host Configuration Protocol动态主机配置协议。机器接入网络后可以通过 DHCP 从网络中的路由器或 DHCP 服务器那里获得 IP 地址同时拿到默认网关、DNS 服务器等配置信息。这样普通用户只需要连上网络系统就能自动完成基础配置。这里有一个很有意思的细节一台刚接入网络的机器还没有 IP 地址也不知道 DHCP 服务器是谁那它怎么发请求答案是广播。机器可以向本地网络中的所有设备发出广播请求大意是“我需要网络配置谁能给我分配地址”在 IPv4 中255.255.255.255可以作为受限广播地址用来向本地网络所有主机发送消息。DHCP 服务器收到请求后再返回可用地址和相关配置。这说明网络协议里有些地址并不表示普通主机而是具有特殊语义。广播地址用于本地网络广播私有地址用于内部网络环回地址用于本机通信。理解这些特殊地址有助于理解网络不是简单的“一个数字对应一台机器”而是一套带有层次和规则的地址体系。十、协议会越来越多但学习不能一开始就被淹没到这里已经出现了很多概念物理信号、比特编码、网卡、总线、以太网、MAC 地址、数据片段、冲突检测、校验和、IP 地址、路由器、DNS、DHCP、广播地址。真实网络世界里还远不止这些。还会有用于动态交换路由信息的协议用于传输网页的 HTTP用于可靠传输的 TCP用于低延迟但允许丢包的 UDP用于安全通信的 TLS用于文件传输、邮件、远程登录、时间同步和各种业务系统的协议。如果一开始就把所有协议一次性铺开学习网络会变得非常痛苦。更好的方式是从一个具体任务出发只在需要的时候引入下一层知识。ping正好适合作为这样的入口。它简单到几乎每个使用计算机的人都见过但又足够底层能把 IP、ICMP、路由、局域网交付、操作系统网络栈等概念串起来。ping关心的问题也足够基础给定一个目标 IP当前机器能不能到达它如果能到达往返需要多久如果不能到达是本地网络不可达、目标机器不存在还是中间路径被阻断虽然ping的输出很简单但它观察的是整个网络路径上的连通性。在 Windows 上系统自带ping.exe。给它一个 IP 地址它就会尝试向目标发送请求。例如8.8.8.8是一个常见的公网地址请求会经过互联网中的多个跳点。再比如192.168.1.60这样的地址通常属于本地私有网络如果这个地址当前没有分配给任何设备本地路由器可能就能判断它不可达并返回相应结果。可以用类似下面的命令测试ping 8.8.8.8也可以测试局域网中的某个地址ping 192.168.1.60从使用者角度看这只是一个命令。从系统角度看背后要经过多层处理。操作系统要构造网络数据包判断目标地址是否属于本地网络必要时把数据交给默认网关如果目标在局域网内还要找到目标的 MAC 地址网卡要把数据发送出去中间设备要按照路由规则转发目标机器收到请求后要生成响应响应再沿着网络路径返回本机最终由ping程序统计耗时和结果。十一、为什么要自己实现 ping直接使用现成的ping工具当然很方便但它隐藏了太多细节。屏幕上看到的只是结果而不是过程。要真正理解网络就不能只满足于“这个命令能用”还要知道它为什么能用以及失败时可能失败在哪一层。自己实现一个ping价值就在于把这些抽象逐层拆开。首先要理解ping使用的不是 TCP也不是普通应用层请求而是 ICMP。然后要理解 ICMP 运行在 IP 之上所以必须知道 IP 包如何构造、如何发送。继续往下还要理解操作系统如何允许用户程序发送这种包为什么某些系统上运行 ping 需要管理员权限为什么有些网络环境会禁止 ICMP为什么目标机器不响应 ping 不一定代表目标服务不可用。再往下还会涉及本地网络交付。即使目标是一个公网 IP本机通常也不是直接把数据发给目标机器而是先发给默认网关。为了把数据交给网关本机需要知道网关在局域网中的 MAC 地址。这就会牵出 ARP 等协议。虽然第一篇还没有进入这些实现细节但它已经把问题链条铺好了ping不是孤立工具而是网络协议栈上的一个观察窗口。学习系统知识时最怕只记名词。MAC、IP、DNS、DHCP、ICMP、路由器、广播地址这些词如果孤立看很容易变成背诵材料。但如果从“我想知道一台机器能不能到达”这个任务出发它们之间的关系就清楚了。要 ping 一个域名先需要 DNS要向目标 IP 发包需要 IP要跨网络发送需要路由器要在局域网交付给下一跳需要 MAC要让机器获得自己的网络配置可能需要 DHCP要在底层发送数据还需要网卡和物理介质。十二、用一条主线理解网络分层可以把前面的内容串成一条主线。最底层的问题是怎样把 0 和 1 从一台机器送到另一台机器这需要电缆、无线信号、光纤等物理介质也需要把比特编码成可传输信号的办法。这个层次关心的是物理世界中的信号传输。再上一层的问题是如果多台机器共享同一片通信介质怎样知道数据来自谁、发给谁怎样避免大家同时发送怎样发现数据损坏这就需要 MAC 地址、数据帧、冲突处理和校验机制。以太网解决的就是这种局域网内部通信问题。继续往上问题变成局域网不能无限扩大怎样把不同网络连接起来这就需要 IP 地址和路由。IP 地址提供层级化寻址路由器根据地址前缀和路由表决定数据应该走向哪里。MAC 地址解决局部交付IP 地址解决跨网络传输两者并不是重复关系。再往上问题变成人的使用体验人不想记数字 IP 地址所以需要 DNS 把域名转换为 IP机器刚加入网络时不想手动配置地址所以需要 DHCP 自动分配 IP、网关和 DNS。再往上才是各种应用协议和工具比如浏览器使用的 HTTP、远程登录使用的 SSH、测试连通性的 ping。这条主线能帮助我们避免把网络学成一堆孤立概念。每一层都在解决上一层暴露出来的新问题每一层也都依赖下一层提供的能力。理解ping的过程其实就是从应用工具一路向下看直到接近软件和硬件的边界。十三、这一篇到底讲清了什么这一篇并不是严格意义上的网络历史课而是用一种构造式的方式解释计算机网络为什么会变成今天这样。早期计算机昂贵所以需要共享终端可以远程访问大型机但那还不是真正的计算机互联多台计算机之间要交换数据就需要网络两台机器可以直接连线多台机器直接全连接会失控共享总线减少了线缆却需要地址、分片、冲突处理和错误检测局域网不能扩展到全世界所以需要路由和 IP人类不愿意记 IP所以需要 DNS机器不愿意手动配置 IP所以需要 DHCP想知道一个目标是否可达就可以从 ping 开始。这套讲法的重点不在于背协议名称而在于理解协议背后的问题。每个协议都是在解决一个具体困难MAC 地址解决共享介质中的身份识别IP 地址解决跨网络路由DNS 解决名字和地址的映射DHCP 解决自动配置ICMP 和 ping 则提供一种观察网络连通性的方式。真正开始写自己的ping时就不会只是机械地构造一个 ICMP Echo Request而是能知道它要被包在 IP 里面IP 又要通过本地网络交付给下一跳下一跳再根据路由表继续转发。最终屏幕上看到的延迟数字其实是数据从本机出发、穿过一层层协议和设备、到达目标再返回的结果。ping之所以适合作为网络学习入口就是因为它足够简单也足够深。简单在于用户只需要输入一个地址深在于它背后连接着整个协议栈。自己实现它就是沿着这条路径把抽象层一层层剥开直到接近软件世界和物理世界的边界。网络看起来复杂但它并不是一开始就复杂。复杂性来自规模、共享、距离、错误、名字、配置和路由等现实问题。每解决一个问题就多一层抽象每多一层抽象系统就更强大也更难一眼看透。理解这些抽象如何产生比单独记住某个协议字段更重要。从这个角度看自己实现ping不只是写一个命令行工具而是在用一个小任务重新走一遍互联网的形成逻辑。先理解为什么要连接计算机再理解怎样连接两台计算机接着理解怎样连接很多计算机最后再理解为什么需要 IP、DNS、DHCP 和 ICMP。等这些关系理顺之后后面真正进入代码实现时每一个系统调用、每一个包头字段、每一次网络响应都会更容易理解。