9.6. 数据包与路由

IP 地址说明了一条消息是发给的。真正负责投递它的机制称为路由,它是一个逐跳的过程,数据包通过它从发送方的本地网络一路传送到接收方的本地网络,二者可能相距非常遥远。

9.6.1. 简述数据包

数据包是 IP 层的工作单元——一段带有小型头部和有效载荷的字节块。头部中有两个对路由很重要的字段:

  • IP 地址(数据包来自何处)。

  • 目的 IP 地址(数据包发往何处)。

有效载荷就是传输层要求网络层投递的任何内容。数据包头部还包含一个生存时间(time-to-live)计数器、一个针对头部的校验和,以及几个控制标志位。这些都不是摄像头的 Python 代码会直接接触的东西。

数据包除了“我们尽力了”之外不承诺任何东西——它们可能丢失、重复,或者乱序到达。可靠性和顺序问题由其上的传输层解决;网络层只是尽其所能地把每个数据包向其目的地转发。

9.6.2. 逐跳传递

数据包离开摄像头,到达第一个不位于摄像头本地网段上的设备:默认网关。(上一页提到过,网络建立时 DHCP 会把网关的地址交给摄像头。)那个设备是一台路由器——它的职责是接收数据包、查看其目的地,然后把它们继续转发出去。

一张展示五个方框的示意图。左侧是一台 标注为“10.0.0.42”的摄像头。它连接到一台 标注为“10.0.0.1 / 203.0.113.5”的路由器。 接着是中间两台未命名的路由器组成的序列。 然后是一台标注为 “198.51.100.1 / 198.51.100.x”的路由器。最后是一台 标注为“198.51.100.20”的服务器。一个带有 “目的地:198.51.100.20”标签的箭头 沿着这条链从左向右移动。

从摄像头发往目的地的数据包在路由器之间逐跳传递,每一跳都更近一步。

路由器有一张路由表——一份“对于匹配此模式的目的地,从此接口把数据包发出去”的清单。对于与摄像头处于同一网络的目的地,表项写着“沿它进来的那条线缆发回去”。对于更广阔互联网上的目的地,表项写着“发给上游路由器”。对于已知模式的目的地(一个企业 VPN、某个特定业务伙伴的网络、一条卫星链路),路由器可能有一条更具体的表项来覆盖默认表项。

上游路由器做的是同样的事情。下一台也是。再下一台也是。每一跳的形态都相同:接收数据包、在表中查找目的地、从正确的接口发出去。最终数据包到达一台确实与目的 IP 处于同一本地网段的路由器。那台路由器完成最后一跳的投递,目的地收到数据包,旅程结束。

9.6.3. 端点并不知道路由

一台向远程服务器发送数据包的摄像头并不知道数据包会如何到达那里。它只知道目的 IP 以及自己默认网关的地址。两者之间的一切——经过哪些路由器、哪些光纤、哪些海底电缆——都是路径上的路由器根据各自的表项一边走一边决定的。路由器自己也只知道它们的直接邻居,以及常见目的地的大致方向;互联网上没有任何单一设备拥有它的完整地图。

正是这种去中心化,使得当个别路径失效时网络仍能继续工作。中间某处一条被切断的线缆,对少数几台路由器来说不过是一次重新路由事件;端点根本不会察觉。这也是为什么一个来自东京摄像头、发往都柏林服务器的数据包能够成功送达,而双方都不需要知道中间隔着哪些国家。

9.6.4. 这对 Python 脚本意味着什么

摄像头在网络层的工作可以归结为:

  • 拥有一个 IP 地址。

  • 知道默认网关的地址(DHCP 会自动填好这一项)。

  • 把寻址到任意 IP 的传出数据包交给那个网关,并信任路径的其余部分。

脚本从不挑选路由,从不指定中间跳点,也从不看见中间的路由器。它把目的 IP 写到数据包上,剩下的就由网络层接管。从 Python 脚本的角度看,整个路由不过是摄像头所加入网络的一项属性——“网关会把数据包发往对我有用的某个地方”。

接下来的传输层假定路由本来就能工作,并在此之上构建可靠性、顺序以及程序到程序的寻址。