动态路由没有中央指挥:每台只做两件事,告诉邻居、听邻居。 所以「距离」不是量出来的,是「邻居说的加一」攒出来的。
7.1.2 说了「要不要动态」,这一节说「动态到底怎么动」。 这里的「互相告诉」是 7.3 两种算法共同的底座。
一、只有两个动作
把动态路由拆到最小,只剩两个动作: 一是「把我表里的东西告诉邻居」,二是「听邻居说的,比我好的就换」。 没有中央服务器,没有全局视图,就这两个动作反复做。 这个设计的好处是「没有单点」: 任何一台挂了,邻居只是收不到它的消息,超时之后把它那条删掉,其余的照常运转。 代价是「收敛需要时间」——因为消息只能一跳一跳传,网的直径越大,传遍越慢。 注意「比我好就换」这句里的「好」:它是协议定义的一个数值,叫度量(metric)。 RIP 用跳数,OSPF 用代价,BGP 用 AS 路径长度。 「好」的标准不同,选出来的路就不同——这是不同协议最本质的差别。
二、为什么是「邻居说的加一」
一台路由器看不到整条路,它只知道「到邻居要多少代价」。 所以它对「到某个远方网段要多少代价」的估计只能是:「邻居说到那儿要 d,我到邻居要 c, 那加起来就是 c 加 d」。 这个推导方式有个名字叫 Bellman-Ford 方程,它是所有距离向量协议的数学基础。 注意它有一个隐含前提:「邻居说的 d 是真的」。 而这个前提在网络变化的那一刻会失效——这就是 7.3.1 要讲的「计数到无穷」。 所以距离向量协议的本质是「基于传闻的路由」:我信我邻居,我邻居信它的邻居,一路信下去。 信得越远,出错的可能性越大——这也是它只适合小网的根本原因。
三、收敛是什么意思
收敛就是「所有人都停止改变」的状态。 拓扑一变,各家的表开始互相影响,改来改去,直到没有一台还想改,就收敛了。 收敛时间由两件事决定: 一是「一跳要多久」(协议的发通告周期),二是「网的直径有多大」(要传几跳)。 RIP 的周期是 30 秒,直径 5 跳的网最坏要两分半。 两分半里,那部分流量是走错的。 这就引出「快收敛」为什么值钱:不是因为「等得烦」,是因为「等的时候在丢包」。 OSPF 改用事件触发加泛洪,把「一跳」从 30 秒压到毫秒级—— 代价是拓扑一变就有一波报文,网络越大这波越猛。
四、两类算法的分岔口
「邻居告诉我什么」这件事有两种答法:
| 答法 | 邻居告诉我 | 我据此算 | 典型协议 |
|---|---|---|---|
| 距离向量 | 到各处的距离 | 加一跳 | RIP |
| 链路状态 | 我周围有哪些链路 | 自己拼图再算 | OSPF |
第一行是「告诉结论」,第二行是「告诉原料」。 告诉结论省事,但结论没法验证;告诉原料费事,但每台都能自己验算。 所以链路状态不会出现「计数到无穷」——因为它手里有地图,不靠传闻。 这个分岔决定了后面所有细节: 报文格式、内存占用、收敛速度、能防哪些错,全部可以从「告诉结论还是告诉原料」推出来。 记住这一句,第七章就不用死记了。
五、动手 Demo:路由表是「互相告诉」出来的
三台路由器串成一条链 A—B—C,只有 A 直连 10.0.0.0/8。 看这个「知道」是怎么一轮一轮传过去的。
INF = 99
names = ['A', 'B', 'C']
dist = {'A': {'10.0.0.0/8': 0}, 'B': {}, 'C': {}}
for rnd in range(1, 4):
newd = {k: dict(v) for k, v in dist.items()}
for i, me in enumerate(names):
for nb in (names[i-1] if i > 0 else None,
names[i+1] if i < 2 else None):
if nb is None:
continue
for dst, d in dist[nb].items():
if d + 1 < newd[me].get(dst, INF):
newd[me][dst] = d + 1
dist = newd
print('第 %d 轮' % rnd,
[dist[m].get('10.0.0.0/8', '-') for m in names])
实跑输出:
--------------------------------------------------------------------
路由表是「互相告诉」出来的
--------------------------------------------------------------------
**动态路由没有中央指挥**:
**每台路由器只做两件事**——**告诉邻居「我知道什么」,听邻居「你知道什么」。**
**重复几轮,整张网就都知道了。**
时间 A 的表 B 的表 C 的表
-----------------------------------------
第 1 轮 距离 0 距离 1 不知道
第 2 轮 距离 0 距离 1 距离 2
第 3 轮 距离 0 距离 1 距离 2
**A 一开始就知道**(它直连那个网段,距离 0)。**B 从 A 那里听到「距离 0」,加一跳变成距离 1。**
**C 再从 B 那里听到「距离 1」,加一跳变成距离 2。**
**第三轮没有任何变化**,说明已经收敛。
**所以「距离」不是你到目标的真实距离**,
**而是「邻居说的距离加一」**——**它是攒出来的,不是量出来的。**
**这一点是后面所有麻烦的根源**:**邻居说的可能是错的,也可能已经过时了。**
A 一开始就知道(它直连那个网段,距离 0)。B 从 A 那里听到「距离 0」,加一跳变成距离 1。 C 再从 B 那里听到「距离 1」,加一跳变成距离 2。 第三轮没有任何变化,说明已经收敛。 注意每一轮里,所有路由器用的是「上一轮的表」——代码里先拷一份 newd 再改,就是这个意思。 如果允许「边算边用」,同一轮里 A 的更新会立刻影响 C,收敛看起来快了,但那不是真实情况—— 真实的路由器是各自独立算的,谁也看不见别人这一轮改了什么。 这也是「仿真」和「实现」的一个常见坑:写模拟器时如果用了「就地更新」,收敛轮数会算少。 要模拟得准,必须按轮次隔离。
30 秒实验
- 算一下:直径 5 跳的网,RIP 最坏要多久才收敛?
- 想一下:为什么每轮必须用「上一轮的表」,不能边算边用?
- 想一下:如果邻居告诉我的是错的距离,我有没有办法验证?
常见误区
- 以为「动态路由有个中心在算」。 没有中心,每台只跟邻居交换信息。
- 以为「距离是量出来的」。 它是「邻居说的加一」攒出来的,所以邻居说错就跟着错。
- 以为「收敛快就是好」。 泛洪快但会抖,网络越大,一次变化掀起的波纹越大。
自测三问(点开对答案)
1. 动态路由的最小动作是哪两个? —— **告诉邻居我的表,听邻居说的、比我好就换。** 就这两个。 2. 为什么距离向量会「互相骗」? —— **因为它只记结论不记来源**,邻居说「我这儿还有路」它就信了。 3. 链路状态为什么不会计数到无穷? —— **它手里有完整的拓扑图**,自己算最短路,不靠传闻。下一节看互联网是「谁管谁」的组织结构。
- ☐ (本节完,下一节 7.2.1 接入互联网的各种组织机构)