哈希表

散列表(哈希表) - 散列函数, 冲突处理, 平均查找长度(ASL)_哔哩哔哩_bilibili

image-20260713145331002

哈希表的底层核心其实就是一个普通的一维数组。为了把任意类型的 Key(比如字符串、对象、大整数)塞进数组里,它设计了三步走的流水线:

  1. 计算哈希值(Hash Code): 当你要存入一个键值对(如 Key="Zhang", Value=99)时,系统先把 Key 喂给一个哈希函数(Hash Function)。这个函数会把抽象的 Key 变成一个很大且杂乱无章的整数(Hash Code)。
  2. 映射数组下标(Index): 算出来的 Hash Code 通常非常大,超出了我们数组的长度。于是算法通过取模运算(Index = HashCode % 数组长度,把这个大整数强行约束在当前数组的合法下标范围内。
  3. 原地存取: 找到了精准的 Index 后,直接把 Value 扔进对应的数组格子 array[Index] 里。查找时同理,顺着这个公式一算,瞬间直达格子取值。

哈希冲突

哈希表看似完美,但它有一个致命的数学Bug——哈希冲突。 因为互联网上的 Key 是无穷无尽的,而我们的数组长度是有限的。根据鸽巢原理,只要数据足够多,必然会出现两个完全不同的 Key,经过哈希函数算完之后,指向了同一个数组下标

image-20260713145710340

1. 拉链法 / 链地址法(Chaining)—— 工业界绝对的标配

  • 做法:数组的每个格子里面存的不直接是 Value,而是一个链表的头指针
  • 冲突处理:如果新来的 Key 算出了相同的下标,别慌,直接把这个新数据挂在当前格子的链表末尾(或头部)
  • 现代进化(红黑树化):在 Java 的 HashMap 中,如果同一个格子里冲突的数据太多,链表变得太长(默认超过 8),为了防止查找效率退化,长链表会自动扭转退化为一棵红黑树,把最坏查找复杂度从 O(n) 死死卡在 O(log n)

2. 开放寻址法(Open Addressing)—— 内存紧凑型选秀

  • 做法:不引入链表等额外结构,所有数据必须老老实实呆在数组里。
  • 冲突处理:如果算出的格子被人占了,新来的数据就顺着数组往后挪,寻找下一个空着的格子
    • 线性探测:挨个格子往后看(+1, +2, +3...),谁空着我坐谁家。
    • 平方探测:按跳跃式往后看(+1², +2², +3²...),防止扎堆聚集。

线性表

线性存储结构和链式存储结构的优缺点

比较维度 顺序存储 (数组) 链式存储 (链表)
物理内存分布 必须严格连续 随机分散,通过指针维系
随机访问能力 (支持下标 O(1) 直达) (必须从头顺序查找 O(n)
插入/删除元素 (需大面积搬移数据 O(n) (只需修改指针连线 O(1)
容量扩展性 固定大小,扩容成本极高 动态按需增减,无限延展
空间利用率 高(100%存数据,无冗余) 较低(需额外存储指针开销)
CPU Cache 友好度 (局部性原理完美利用) (容易引发缓存缺失)

邻接表和邻接矩阵

一、 邻接矩阵(Adjacency Matrix)

image-20260714110657901

邻接矩阵的底层灵魂非常纯粹:用一个 V × V 的二维数组来直接表示顶点之间有没有边。

假设图中有 N 个顶点,我们就开辟一个 N × N 的大方阵 matrix[N][N]

  • 无权图:如果顶点 i 到顶点 j 有边,则 matrix[i][j] = 1;如果没有边,则 matrix[i][j] = 0
  • 带权图(网):如果顶点之间有边,格子里就直接存边的权值(如距离、花费);如果没有边,通常用一个无穷大(INT_MAX)来占位。

优点:

  1. 极速查找任意边(O(1) 时间):你想知道顶点 3 和顶点 5 之间有没有连接?直接一行代码 if(matrix[3][5] == 1) 就能在常数时间内瞬间秒杀,读写性能极高。
  2. 计算图的度(Degree)很方便:对于无向图,想知道顶点 i 连接了几个邻居,只需要整整齐齐地把第 i 行(或第 i 列)的所有元素加起来就行了。
  3. 矩阵乘法的数学外挂:邻接矩阵可以直接进行矩阵运算。例如,把邻接矩阵进行平方(A2),得到的矩阵中 matrix[i][j] 的值,恰好代表顶点 i 到顶点 j 长度为 2 的路径有多少条。这在图的高级算法(如动态规划、图神经网络 GCN)里是非常强悍的数学工具。

缺点:

  1. 恐怖的空间开销(O(V2) 空间):不管图里的边多还是少,只要顶点数是 V,就必须死板地申请 V2 个格子。
  2. 不擅长处理“稀疏图(Sparse Graph)”:如果一个图有 1 万个顶点(V = 10000),但一共只有 2 万条边(E = 20000)。邻接矩阵会强行申请 1 亿个格子,其中 99.98% 的格子里面存的都是 0。这种对内存的暴殄天物被称为稀疏矩阵的空间浪费

二、 邻接表(Adjacency List)

image-20260714121749888

为了外挂拯救稀疏图下的内存崩溃,科学家们引入了邻接表。它的核心设计类似于哈希表的拉链法:用一个一维数组存下所有顶点,数组的每个格子代表一个顶点,后面挂一个单链表,链表里密密麻麻串联的,全都是它的亲密邻居。

构成组件:

  1. 顶点表(主干数组):一个大小为 V 的普通数组,用来存放顶点的基本信息以及指向它第一个邻居的链表头指针。
  2. 边表(单链表):链表中的每个节点代表一条真实的边,节点内部通常包含:[邻居顶点的下标 | 边的权值(可选) | 指向下一个邻居节点的指针 next]

优点:

  1. 极高的空间利用率(O(V + E) 空间):它是典型的“给多少钱办多少事”。有多少个顶点就建多大的数组,有多少条边就动态申请多少个链表节点。绝不为不存在的边浪费一个字节的内存,完美通杀稀疏图。
  2. 找所有人所有的邻居极其丝滑:如果算法需要从顶点 i 出发去遍历它的所有邻居(比如在进行 BFS 广度优先搜索DFS 深度优先搜索 时),邻接表只需要顺着第 i 个格子的单链表一路往后走,指针指到谁就是谁,没有任何做无用功的空扫描。

缺点:

  1. 查找特定边时被迫变慢:如果你冷不防地想查一下“顶点 3 和顶点 999 之间有没有边?”,在邻接表里你必须先定位到数组的第 3 格,然后开始遍历它后面的长链表,最坏时间复杂度退化为 O(V)(或者顶点的最大度数)。
  2. 计算“入度(In-degree)”很痛苦(针对有向图):在有向图中,邻接表默认只能轻松查到“我指向了谁(出度)”。如果想知道“谁指向了我(入度)”,必须把整个图里所有顶点的链表全量扫描一遍才能统计出来(工程上的解法是额外再建一棵逆邻接表)。

连通

一、 无向图的连通:只要能走到就行

image-20260714104452655

在无向图中,边的方向是双向通行的。它的连通概念非常纯粹:

  1. 连通(Connected)

如果从顶点 u 到顶点 v 之间存在至少一条路径,我们就说这两个顶点是连通的。

  1. 连通图(Connected Graph)

如果一个无向图中,任意两个顶点都是连通的(从任何一点出发,都能顺着边走到其他任何一点),那这个图就叫连通图。

  1. 连通分量(Connected Component)

这是一个极高频的八股概念,定义叫做:无向图的极大连通子图

  • 物理直觉:就是一个孤立的“朋友圈”。这个圈子内部所有人都能互通,但圈子外面的人绝对进不来。一个非连通的无向图,就是由多个互不相交的连通分量组合而成的。
  • 关键词:极大。意思是“能加进来的邻居都加进来了,再多加一个点,它就不连通了”。

二、 有向图的连通:方向决定一切(强连通 vs 弱连通)

image-20260714105814954

有向图的边是单行道,所以连通的难度直接翻倍。

  1. 强连通(Strongly Connected)

对于一对顶点 uv,如果既存在一条从 u → v 的有向路径,同时也存在一条从 v → u 的有向路径(也就是两人能礼尚往来,互相直达),这才叫强连通。

  1. 强连通图(Strongly Connected Graph)

有向图中,任意两个顶点之间都是强连通的

  • 最少边数考点:一个含有 n 个顶点的有向图,如果它是强连通图,最少需要 n 条边(刚好围成一个封闭的大环)。
  1. 强连通分量(Strongly Connected Component, SCC)

有向图的极大强连通子图。在工业界(如社交网络作弊团伙挖掘、网页 PageRank 计算),寻找 SCC 是非常核心的业务。

  1. 弱连通图(Weakly Connected Graph)

如果一个有向图本身不是强连通的,但如果你耍赖把所有有向边的箭头全部抹去,强行当成无向图来看,它变成了一个连通图。那原图就叫做弱连通图。

生成树

image-20260714110112649

一棵树如果想自称是原图 G 的“生成树”,它作为子图必须严格满足以下四个几何约束:

  1. 顶点全覆盖:它必须包含原图的全部 n 个顶点,一个都不能少。
  2. 边数死死锁死(n − 1 条):如果原图有 n 个顶点,它的生成树必须且只能有 n − 1 条边
    • 多一条边:必然会在线路中长出闭环(形成环路,就不能叫“树”了)。
    • 少一条边:图就会瞬间断裂,碎成不连通的两块。
  3. 绝对连通且无环:树的天然属性,任意两点之间有且仅有一条路径。
  4. 极小连通子图:这是最容易考的定义题。它是保证图连通的边数最少的子图

💡 避坑小锦囊:一棵连通图的生成树不是唯一的。同一个图,只要你砍掉不同的冗余边,就能组合出形态各异的生成树,但它们的共同点是都拥有 n 个点和 n − 1 条边。

最小生成树-Prim(普里姆)算法和Kruskal(克鲁斯卡尔)算法

维度 Prim (普里姆) Kruskal (克鲁斯卡尔)
核心出发点 顶点(从一个点向外滚雪球) (每次挑最便宜的边拼接)
底层数据结构 邻接表/邻接矩阵 + 优先队列 (小顶堆) 边集数组 + 并查集 (DSU)
中间状态 过程中始终只有一棵逐渐变大的树 过程中会诞生很多孤立的小树林,最终合并
时间复杂度 O(ElogV)O(V2) O(ElogE)
首选战场 稠密图E ≈ V2,边很多) 稀疏图E ≈ V,边很少)
  • 为什么稀疏图选 Kruskal?

    因为稀疏图里边很少(E 很小),Kruskal 第一步的边排序速度像闪电一样快。

  • 为什么稠密图选 Prim?

    因为稠密图里边多得像乱麻。Kruskal 对几十万条边排序会直接卡死;而 Prim 滚雪球的次数只和顶点数 V 相关,能极高地避开边多带来的开销。

什么是最小生成树?

一棵树想要被冠以“最小生成树”的称号,必须在带权无向连通图中严丝合缝地满足以下四个底层特征:

  1. 顶点全覆盖(不漏一人)

    它必须包含原图中的全部 N 个顶点,任何一个节点都不能掉队。

  2. 边数死死卡死(不多不少)

    对于 N 个顶点的图,最小生成树必须且只能包含 N − 1 条边

    • 多一条:必然会在网络中滋生出冗余的“闭环”,电网就会短路。
    • 少一条:电网就会断裂,部分城市会沦为孤岛。
  3. 连通且无环

    任意两个城市之间有且仅有一条通信路径,结构极其精简。

  4. 总权值最小

    这是它灵魂的一点。在原图所有能挑出的、满足上述三条的“生成树”集合中,这 N − 1 条边的权值相加之和是最小的

Prim

image-20260714123401412

Kruskal

image-20260714123659703

最短路径

Dijkstra(迪杰斯特拉)算法

image-20260714124227164

Floyd

image-20260714125331686
image-20260714125258805
image-20260714130111691

排序

稳定性问题

image-20260713143137551

快排时间复杂度什么情况下退化

场景一:数据已经“完全有序”或“完全逆序” + 选用边界元素做基准(最经典八股)

假设你有一组已经排好序的数据 [1, 2, 3, 4, 5],而你的快排代码每次都盲目地选用第一个元素(或最后一个元素)作为基准值(Pivot)。

  • 第一次划分:选 1 为基准。比 1 小的在左边(0个),比 1 大的在右边(4个:[2, 3, 4, 5])。
  • 第二次划分:对右边递归,选 2 为基准。左边 0 个,右边 3 个([3, 4, 5])……
  • 代价:由于每次切一刀,都只能切掉一个孤零零的元素,递归的深度直接从 log n 飙升到了 n。总共需要进行 n 次划分,每次划分要遍历剩下的一切设备,总比较次数就是 $n + (n-1) + (n-2) + ... + 1 = \frac{n(n+1)}{2}$,时间复杂度无悬念退化为 O(n2)。此外,过深的递归层数还极易引发栈溢出(Stack Overflow)

场景二:数据中包含“大量高度重复”的元素

如果一个数组里有几万个数据,但它们的值全都一模一样(或者绝大多数都一样),例如:[5, 5, 5, 5, 5]

  • 在传统的单向朴素划分机制(如 Lumoto 划分)下,算法很容易把所有等于基准值的元素全部强行划归到某一个子阵营(比如全部推到右边)。
  • 这就会再次造成左边 0 个、右边 n − 1 个的极端偏瘫结构,导致时间复杂度同样瞬间崩塌至 O(n2)

1. 随机化选取基准(Randomized Quick Sort)

  • 做法:不再死板地选第一个或最后一个元素,而是每次在当前区间内随机盲抽一个元素作为 Pivot,并与边界元素交换后再执行划分。
  • 物理意义:这样把快排的命运从“看输入数据的脸色”变成了“看随机数发生器的脸色”。哪怕输入数据故意设计得再有序,在随机抓取下,每次都能抓到极端的概率降到了无限趋近于 0,在概率学上强行把期望复杂度死死锁在 O(nlog n)

2. 三数取中法(Median-of-Three)

  • 做法:这是现代工业库(如许多 C++ std::sort 的底层变体)最青睐的做法。每次挑选当前区间的最左端、最右端和正中间这三个数,进行简单排序后,挑出大小排在第二的那个“中位数”作为基准值
  • 物理意义:只要选出的不是极端极大或极小值,就能稳稳保证每一次切下去,左右两边多多少少都有人,绝不会长成单链表。

3. 三路快排(3-Way Quick Sort)针对重复元素

  • 做法:改变划分规则,将数组强行切成三块:【小于基准值】 | 【严格等于基准值】 | 【大于基准值】
  • 物理意义:划分完毕后,中间那一整块【严格等于基准值】的大量重复元素在接下来的递归中直接被忽略,不需要再参与任何排序。算法只需继续向左、向右递归。这个外挂让快排在面对大量重复数据时,时间复杂度不仅不会退化,反而会大幅超越 O(nlog n),甚至逼近 O(n)

二叉搜索树(二叉排序树)(二叉查找树)

数据结构合集 - 二叉搜索树(二叉排序树)(二叉查找树)_哔哩哔哩_bilibili

左子树铁律:若它的左子树不为空,则左子树上所有节点的值都必须严格小于它的根节点的值。

右子树铁律:若它的右子树不为空,则右子树上所有节点的值都必须严格大于它的根节点的值。

递归性质:它的左、右子树也必须各自是一棵完美的二叉排序树。

image-20260713153227527

平衡二叉树

平衡二叉树(AVL树)_哔哩哔哩_bilibili

一棵二叉树想要自称是“平衡二叉树”,必须同时满足以下两个条件:

  1. 它首先必须是一棵二叉搜索树(BST):左小右大。
  2. 核心平衡条件(严格限高)任意节点的左子树和右子树的高度差的绝对值不能超过 1

核心概念:平衡因子(Balance Factor, BF)

在工程实现中,我们用“平衡因子”来量化一棵树是否失衡:

平衡因子 (BF) = 左子树的高度 − 右子树的高度

  • 只要整棵树中所有节点的平衡因子只能是 −101,这棵树就是平衡的。
  • 一旦某个节点的平衡因子绝对值大于 1(变成了 ±2),这棵树就拉响了“失衡警报”,必须立刻进行结构调整。
image-20260712202501173

平衡二叉树(AVL 树)是在二叉搜索树的基础上,通过限制任意节点左右子树高度差绝对值不超过 1来维持动态平衡的结构。它利用 LL/RR/LR/RL 四种旋转机制防止树退化为单链表,从而稳定保证了 O(log n) 的极速查找时间复杂度。

二叉搜索树性能退化的场景

二叉搜索树(BST,Binary Search Tree)的性能退化,核心原因只有一句话:当数据的插入顺序过于规律,导致二叉树失去了分叉的“金字塔”结构,硬生生退化成了一条“单链表”

在理想的满二叉树或完全二叉树状态下,BST 的查找、插入、删除时间复杂度都是完美的 O(log n)。但在以下几种经典的工程场景中,它会直接发生灾难性的退化:

1. 严格递增或递减插入(最经典的面试八股场景)

如果你按照从小到大(或从大到小)的顺序,连续往一棵空的二叉搜索树里插入数据,例如:[1, 2, 3, 4, 5]

  • 插入 1:作为根节点。
  • 插入 2:比 1 大,挂在 1 的右边。
  • 插入 3:比 1 大,比 2 大,挂在 2 的右边……

最终这棵树会毫无悬念地长成一根严格向右倾斜的单链表

  • 代价:此时的树高直接等于节点总数 n。树的左右分支极其不平衡,每次查找末尾元素都要从头遍历到尾,时间复杂度直接从 O(log n) 惨烈退化成了 O(n)

2. 交替边界插入(长成蛇形“之”字树)

如果插入的数据总是在当前数据集的极大值和极小值之间反复横跳,例如输入顺序为:[5, 1, 4, 2, 3]

  • 5 是根节点,1 挂在 5 的左边,4 挂在 1 的右边,2 挂在 4 的左边……
  • 最终整棵树虽然不是笔直的,但它会形成一条没有多余分叉的“之”字形长蛇。由于它本质上依然是一条单链表,树高并没有降低,因此性能同样会退化为 O(n)

红黑树

image-20260712203650441

规则

规则一:每个节点或者是红色的,或者是黑色的。

规则二根节点永远是黑色的

规则三每个叶子节点(指的是最底层的空节点 NILNULL)都是黑色的

规则四如果一个节点是红色的,则它的两个子节点必须是黑色的。(也就是说:绝不能有两个连续的红节点搭在一起)。

规则五:对任意一个节点而言,从该节点到其所有后代叶子节点的宣告路径上,所包含的黑色节点数量必须完全相同(这个数量被称为黑色高度)。

B树

为什么需要B树

image-20260713090702105

减少磁盘 I/O 块的读取

在底层设计中,B树的一个节点的大小通常会直接设置为操作系统的一个磁盘页(Page,一般是 4KB 或者是其整数倍)

这样,每次进行一次磁盘 I/O,就能把一个节点里成百上千个键值一口气全部读进内存。

极度矮胖

假设一个节点能存 100 个键值。一棵 3 层的 B树,最多可以容纳 100 × 100 × 100 = 100 万个数据!而要把 100 万数据存在红黑树里,高度可能有接近 20 层。

20次磁盘 I/O vs 3次磁盘 I/O,在性能上这就是几百倍的降维打击。

核心定义(以 m 阶 B树为例)

image-20260713100131224

B树是一种多路自平衡的搜索树。所谓“m 阶”,指的是一个节点最多能长出 m 个分叉(子节点)

一棵标准的 m 阶 B树必须满足以下严苛的平衡性质:

  1. 节点多值性:每个节点内部不再只存一个 key,而是存一组排好序的多个键值(Keys)*和对应的*数据指针
  2. 分支数的约束
    • 根节点至少有 2 个子节点(除非整棵树只有根节点一个)。
    • 除了根节点外,所有内部节点至少有 m/2⌉ 个子节点,最多有 m 个子节点。
  3. 键值与分支的关系:如果一个节点包含了 k 个键值,那它必定拥有 k + 1 个子节点分叉
    • 例如:节点里存了 [10, 20] 两个数,那它下面就会分出 3 个树枝,分别对应  < 1010 ∼ 20 > 20 的数据区间。
  4. 绝对的叶子对齐所有叶子节点都必须在同一层。B树不允许任何一个分支掉队,它是绝对完美的平衡。

B+树

数据结构合集 - B+树_哔哩哔哩_bilibili

为什么需要B+树

痛点一:B树节点“带货”导致分叉数受限,树还不够矮

💥 B树的问题:

在 B树中,每个节点(无论是根节点、内部节点还是叶子节点)里面都同时存储了键值(Key)和对应的真实数据(Data/Record)

我们知道,计算机从磁盘读写的基本单位是磁盘页(Page,通常为 4KB 或 16KB)。因为真实数据(比如一条包含几十个字段的会员信息)占用的字节数非常大,导致一个 16KB 的磁盘页节点里面塞不下几个键值,分叉数(阶数 m)严重受限。分叉数一少,当数据量暴涨到千万级时,B树的高度还是会不可避免地抽高,从而引发更多的磁盘 I/O。

✨ B+树的进化(上层纯路由):

B+树做出了极其激进的阶级分化:非叶子节点变成了纯粹的“导航路由”,里面只存 Key 和指针,绝对不带任何 Data 拖油瓶!所有的真实数据被悉数打包,全部踩压、堆放在最底层的叶子节点中。

  • 工程红利:由于非叶子节点不占地方,一个 16KB 的页可以疯狂塞入上千个 Key,分叉数瞬间暴涨。这使得 B+树达到了极致的“矮胖”,通常一棵 3 到 4 层的 B+树,就能轻松存储 上千万甚至数亿条 级别的数据。在大海捞针时,最多只需要 3 到 4 次磁盘 I/O

痛点二:B树进行范围查询时,存在恐怖的“全树回溯”

💥 B树的问题:

在日常的 SQL 业务中,范围查询(比如 WHERE age BETWEEN 18 AND 25)或全表扫描是高频刚需。

如果用 B树,算法在树里定位到 18 后,想要找下一个比它大的数,必须频繁地在树的各个父子、兄弟节点之间上下回溯、进行复杂的左中右中序遍历。在磁盘文件系统里,这种跨节点的上下跳跃会导致大量跨页的随机磁盘 I/O,磁头疯狂寻道,性能瞬间雪崩。

✨ B+树的进化(底部双向链表):

B+树在最底层放了一个物理大外挂:所有叶子节点之间,通过指针紧密地首尾相连,织成了一条环环相扣的双向链表

  • 工程红利:算法通过根节点一路向下,做一次 O(log n) 的单点定位找到 18 所在的叶子节点。接下来的事情变得无比丝滑——不需要再回溯哪怕一次上层节点,直接顺着底部的双向链表一路横向往后进行线性指针扫描,直到看到 25 为止。随机 I/O 瞬间变成了极其高效的顺序 I/O。

痛点三:B树的查询效率上下抖动,不够稳定

💥 B树的问题:

在 B树中,因为每个节点都有数据,如果运气好,要找的主键刚好在根节点,那 O(1) 的时间就能高潮返回;如果运气不好在最底层的叶子节点,则需要 O(log n)。这在内存中无所谓,但在磁盘 I/O 敏感的数据库系统里,这会导致网络响应时间发生明显的抖动和不稳定

✨ B+树的进化(路径一视同仁):

在 B+树中,非叶子节点没有数据,任何数据的查询路径长度都是完全相同的——都必须老老实实从根节点走到最底层的叶子节点。

  • 工程红利:这种“一视同仁”保证了每一次 SQL 查询的响应时间都极度稳定、可预测,为高并发系统的吞吐量提供了坚实的下限保障。

b树和b+树的区别

image-20260713131105692
对比维度 B树 (B-Tree) B+树 (B+ Tree)
数据(Data)存储位置 所有节点(根节点、内部节点、叶子节点)都存数据。 仅最底层的叶子节点存储数据,非叶子节点只存索引键(Key)和指针。
叶子节点间关系 彼此孤立,没有指针连接。 首尾相连,通过双向链表紧密连接。
相同数据量下的树高 相对较高(节点因带货导致分叉数少)。 极其矮胖(非叶子节点能容纳更多Key,分叉数极大)。
单点查询效率 不稳定。运气好 O(1) 在根节点找到,运气差 O(log n) 极其稳定。任何查询都必须一路走到最底层叶子节点,稳定在 O(log n)
范围查询 / 全表扫描 代价极高。需要在树的各层节点之间反复上下回溯(中序遍历)。 极其丝滑。只需单点定位到起点,然后顺着底部链表横向线性扫描
Key 的唯一性 树中所有 Key 不重复。 非叶子节点的 Key 会在子节点中重复出现(起区间路由作用)。
image-20260713131702330

并查集

image-20260714131103966
image-20260714131243444

堆(Heap)

完全二叉树

铁律一:除了最底层外,其余各层必须是满的。

如果一棵树有 h 层,那么从第 1 层到第 h − 1 层,每一层的节点数都必须达到最大值。例如第 1 层有 1 个,第 2 层有 2 个,第 3 层有 4 个,第 4 层有 8 个……绝不能有任何一个空位。

铁律二:最底层的节点必须“绝对靠左对齐”。

最底层的节点可以不满,但所有节点必须从左到右连续排列,中间绝对不能有任何空隙。只有左边塞满了,才能去填右边。

它是一棵动态的、绝对对齐的完全二叉树,它的终极使命是让你在 O(1) 的时间内瞬间拿到当前这堆数据里的“最大值”或“最小值”。

一棵树想要称自己为“堆”,必须同时满足以下两个条件:

  1. 结构性质(完全二叉树):除了最底层外,其余各层都是满的;且最底层的节点都连续集中在左边。这个性质决定了堆有一个绝妙的外挂——可以用连续的一维数组来直接存储,根本不需要指针
  2. 堆次序性质(Heap Property):任意节点的值都大于或等于(或小于或等于)其子节点的值。

根据堆次序的不同,堆分为两大流派:

  • 大顶堆 / 最大堆(Max-Heap):根节点是整棵树的最大值,任何父节点的值都大于等于它的孩子。
  • 小顶堆 / 最小堆(Min-Heap):根节点是整棵树的最小值,任何父节点的值都小于等于它的孩子。

网站推荐

题库 - 八股精

简单回顾attention流程

1
2
3
4
5
6
7
8
9
10
11
12
13
【输入向量 X】(来自上一层或Embedding)

├─── 步骤①:解耦分裂 ───> 动态乘以 Wq, Wk, Wv 权重,生成 Q, K, V 矩阵
│ (此时 V 矩阵诞生了,和 FFN 还毫无关系)

├─── 步骤②:注意力对齐 ─> 计算 Q 乘以 K 的转置 (Q·K^T),得到亲疏得分矩阵

├─── 步骤③:归一化 ─────> 经过 Softmax 激活,把得分变成加起来等于 1 的权重

├─── 步骤④:财富提取 ─────> 用 Softmax 的权重去乘以 V 矩阵 (Softmax·V)


【Attention 层的最终输出】 ───> 这时,一次完整的 Attention 计算宣告结束!

qkv矩阵的含义是怎样的

我觉得可以从他们本身的名字入手

1. Q (Query - 查询)

  • 含义:当前词“试图去关注(或询问)其他词”的向量表示。
  • 角色:它是主动方。在 Self-Attention 中,每个词都会轮流作为 Q,去询问句子里的所有词(包括它自己):“你们谁和我有关系?关系有多大?”

2. K (Key - 键)

  • 含义:当前词“被其他词关注时的特征(或身份标签)”的向量表示。
  • 角色:它是被动方。它存在的意义就是和 Q 做点积(Dot Product),来计算两个词之间的相关性或相似度。

3. V (Value - 值)

  • 含义:当前词“本身所包含的、最核心的内容信息”。
  • 角色:一旦 QK 算出了注意力权重(即谁和谁更相关),这些权重就会乘到对应的 V 上。最终把这些加权后的 V 累加起来,就得到了该词融合了上下文信息后的新表示。

我的理解:

q可以理解成新词关注历史字符的向量,也就是query,k则是历史字符被关注时的向量,两者相乘可以获得关联度

v可以代表每一个词在进入这一层时,通过与权重矩阵相乘,提取出了它最核心的、未经污染的语义特征(比如“苹果”包含了:水果、甜的、红的、科技公司、iPhone 等属性)。这个 V 就是这个词的“属性本体”。

kvcache是什么

在多次对话的过程中,前面的历史字完全没变,它们算出来的 K(身份标签)和 V(词本身的属性)也就是固定不变的。那算过一次之后,直接把它们存在内存(显存)里,后面就可以直接拿来用,因此降低了计算量,提高了响应的速度

那为什么不存q矩阵呢

这个问题的关键还是要理解qkv矩阵的含义和kvcache的流程

q矩阵代表着最新字符对于历史字符关注的向量,而新字符是不对吐出来实时变化的,所以存储q矩阵没有意义

而这里要区分一下大模型生成的两个阶段:

阶段一:Prefill(预填充阶段 —— 读懂你的 Prompt)

当你把一句话(比如“请问什么是Attention”)发给模型时,模型会一次性把这几个字全部输入进去。

  • 这个时候,所有词都要互相看。所以每个词的 QKV 全都要计算,以此来捕捉整个句子的上下文语义。
  • 计算完后,模型会把这些历史词的 KV 老老实实地存进内存里。这就是 KV Cache 的初始化

阶段二:Decoding(解码阶段 —— 逐字生成)

这就是你刚才描述的绝妙场景。假设模型现在吐出了一个新词:“它”

  • Q 是实时的、一次性的

    “它”这个新词作为当前的唯一主角,会产生一个崭新的 Q。它需要拿着这个 Q 去跟前面所有的历史词进行比对。

    💡 为什么不用缓存 Q 因为当“它”这个词处理完、吐出下一个词(比如“是”)之后,主导权就交给了“是”的 Q。旧的 Q 在未来的生成中永远都不会再被用到了。它是一次性的消耗品。

  • KV 必须死死存住

    为了算出“它”和历史词的关联度,你需要历史词的 K;为了融合出“它”的新向量,你需要历史词的 V

    • 如果我们不缓存 KV,每生成一个新词,模型就得把前面所有的历史词重新做一遍矩阵乘法,去重新算一遍它们的 KV
    • 随着生成的句子越来越长,这种重复计算会呈爆炸式增长(计算量 O(N2)),模型会越写越卡。
    • 有了 KV Cache:历史词的 KV 已经在内存里躺着了。新词“它”算完自己的 QKV 后,拿自己的 Q 去和内存里的一大推历史 K 做乘法,算完后,再顺手把自己的 KV 也追加到缓存的末尾,供下一个词使用。

ViT(Vision Transformer)

ViT(Vision Transformer) 是谷歌在 2020 年提出的突破性图像识别模型(论文名:《An Image is Worth 16x16 Words》)。

它的核心贡献在于:打破了计算机视觉(CV)领域由卷积神经网络(CNN)绝对统治的局面,直接将原本用于自然语言处理(NLP)的 Transformer 架构生搬硬套(几乎一字未改)地应用到了图像分类任务上,并且取得了超越经典 CNN 的效果。

ViT 的核心架构与工作流程

为了让擅长处理一维文本序列的 Transformer 理解二维图像,ViT 将图像进行了“文本化”处理。整个前向传播的流程如下:

image-20260601210911553

1.图像切块与展平 (Patch Partition & Flattening):Step 1。

输入图像无法像文本那样直接输入。ViT 首先将一张宽高为 H × W 的图像,切分成一个个固定大小的图像块(Patches),比如 16 × 16 像素。如果图像是 224 × 224,就会被切成 14 × 14 = 196 个块。每个块被展平成一个一维向量。

2.线性映射与标量化 (Linear Projection of Flattened Patches):Step 2。

展平后的图像块通过一个线性变换层(Linear Layer),映射到指定的特征维度(Embedding Dimension),这一步就相当于 NLP 中的 Word Embedding,把图像块变成了 Visual Tokens

3.引入 Class Token 与位置编码 (Position Embedding):Step 3。

  • [class] Token:参考了 BERT 的做法,在所有图像 Token 最前面拼一个可学习的分类 Token。经过多层 Attention 交互后,该 Token 沉淀出的特征将直接用于最后的分类预测。
  • Position Embedding:因为标准自注意力机制是“词序无关”的,ViT 为每个 Token 加上了一维的、可学习的位置编码,以此保留图像块之间的空间相对位置关系。

4.Transformer 编码器堆叠 (Transformer Encoder):Step 4。

拼好位置编码的 Token 序列进入标准的 Transformer Encoder。内部包含多头自注意力机制(Multi-Head Attention)MLP 块。由于自注意力机制的存在,任意两个图像块之间都可以直接计算相关性,具备全局视场。

5.MLP Head 分类输出:Step 5。

经过多层 Encoder 交互后,提取出第一个位置(即 [class] Token)对应的输出特征,送入全连接层(MLP Head),最终预测出图像所属的类别(如 Bird, Ball, Car)。

为什么使用编码器而不是解码器

在 Transformer 架构中,选择使用 Encoder(编码器) 还是 Decoder(解码器),本质上是由注意力机制的方向性(Attention Directionality)任务的本质(Task Nature)决定的。

1. 双向注意力 vs. 单向因果注意力

这是最根本的技术差异:

  • Encoder 采用“双向自注意力”(Bi-directional Attention):序列中的任意一个 Token 都可以看到其他所有位置的 Token。对于图像而言,左上角的“猫头”和右下角的“猫尾”在空间上是并存的,它们之间需要进行自由的相互双向感知。
  • Decoder 采用“因果掩码自注意力”(Masked/Causal Attention):为了能够预测下一个词,Decoder 强制引入了掩码,使得第 i 个位置的 Token 只能看到它前面的 Token,绝对不能“偷看”后面的 Token。

如果在 ViT 中使用 Decoder 会发生什么?

图像块(Patches)是被强行拉成一维序列输入的。如果使用 Decoder,排在序列前面的图像块(如图像顶部)就无法感知排在后面的图像块(如图像底部)。因为图像是一个二维的、并行的整体,它不具备文本那种天然的、从左到右的时间先后顺序(因果律)。强制使用单向掩码会人为地割裂图像的全局空间联系。

2. 特征理解(Understanding) vs. 自回归生成(Generation)

两者的架构设计是为了服务完全不同的使命:

  • Encoder 的使命是“压缩与理解”

    它的目标是将输入的完整信息(如整张图片或整段话)进行深度的特征抽取与融合,最终在顶层沉淀出一个高质量的特征向量(例如 ViT 中的 [CLS] Token)。这个向量包含了全局的上下文信息,非常适合下游的分类、检测、分割等“理解型”任务。

  • Decoder 的使命是“预测与生成”

    它的设计是为了自回归(Autoregressive)地输出一个序列——根据已有的信息,一个词一个词地往后“吐”(如 LLM 的文本生成)。它不需要一次性理解全部输入来做单次分类,而是需要维持“前因后果”的生成闭环。

3. 图像的高冗余性需要全局视野

文本是高度人工抽象化的符号,信息密度极高;而图像是高维、高冗余的信号(比如连续好几个图像块可能都是同一片蓝天)。

处理图像时,模型迫切需要大刀阔斧地进行全局对比和信息凝练。Encoder 的全局无死角注意力机制,能够用最短的路径(只需一层 Attention)建立起全图任意两点之间的关联,从而高效地剔除冗余,把整张图的能量集中到核心目标特征上。

多模态大模型的来时路

原生多模态 vs 拼接多模态

❶ 拼接流派(代表:LLaVA、Qwen-VL、InternLM-XComposer)

1
2
3
[图片] ┐
├──> [统一分词器 Unified Tokenizer] ──> [统一多模态主干网络 Unified Backbone]──>输出
[文本] ┘

1.图片经过一个独立的“Vision Encoder”(视觉编码器,通常用 CLIP,参数量约 3B 左右)提取出视觉特征。

2.这些特征穿过中间那个黄色的“Connector”(连接器/投影层,通常就是一个简单的 MLP 线性层),被强行翻译成文本大模型能听懂的“视觉代号(Visual Tokens)”。

3.随后,这些视觉代号和你的文字提示词(Text Tokens)拼在同一排,一起送进最后那个巨大的“Large Language Model”(大语言模型,如 Llama,参数量通常为 7B、13B 或更大)。

为什么这种架构天生容易产生“物体幻觉”?

  • 地位不对等: 在这个架构里,最后的 LLM 是绝对的主宰,它拥有 90% 以上的参数量和绝对的“话语权”。
  • 信息的深层稀释: 视觉信号只在最开始输入时露了个脸。当模型自回归地一个字一个字往下生成时,随着文本越写越长,自注意力机制(Self-Attention)的焦点会不可避免地向左移——也就是疯狂关注自己前面刚说过的文字(Over-Trust 偏置),而最左边的视觉代号在层层传递中被严重稀释。最终,模型体内的语言先验(肌肉记忆)战胜了视觉,导致闭眼瞎编。

❷ 原生流派(代表:GPT-4o、Google Gemini、Chameleon)

1
2
3
[图片] ┐
├──> [统一分词器 Unified Tokenizer] ──> [统一多模态主干网络 Unified Backbone]──>输出
[文本] ┘

注意看输入端,它没有相互独立的“视觉视觉编码器”和“文本编码器”,而是图片和文本在最底层就一起进了一个“Unified Tokenizer(统一分词器)”。

在这里,图片不再是被提取特征后“嫁接”过来的外来物。像素信号和文字信号在出生(预训练)的第一天起,就被切成了同等地位的代号,手拉手一起送进底部的“Unified Backbone(统一主干网络)”。

qwen3vl与qwenvl

Qwen-VL 更像是 CLIP式视觉编码器 + 一个浅桥接层 + 文本LLM; Qwen3-VL 已经变成了 原生长上下文/视频友好的统一多模态架构,视觉侧和语言侧耦合得更深。

image-20260528155058834

Qwen3-VL 属于“组合式架构”(Compositional Architecture),而非通过单一 Transformer 从零训练的“原生”模型;但从能力表现和训练深度上看,它实现了“原生级”的融合体验。Qwen3-VL 采用了经典的三模块架构:

视觉编码器 (Vision Encoder):基于 SigLIP-2。 ▪ We utilize the SigLIP-2 architecture ▪ NaViT: native resolution

视觉-语言融合层 (Merger):基于 MLP(多层感知机)。 ▪ As in Qwen2.5-VL, we use a two-layer MLP to compress 2 × 2 visual features from the vision encoder into a single visual token, aligned with the LLM’s hidden dimension. ▪ 16 * 16 => 32 * 32

大语言模型 (LLM):基于 Qwen3。

虽然架构是组合的,但报告中提到它“原生支持交错上下文(natively supports interleaved contexts)”。这里的“原生”更多是指功能层面——它能够像处理文本一样自然地处理图文混排、视频流,并支持 256K 超长上下文)。

Qwen3-VL 的团队明确提出了一种“分而治之 (divide-and-conquer)” 的策略。

• 先分别开发最强的视觉感知能力(SigLIP-2)和语言推理能力(Qwen3)。 • 然后再通过协同的方式将它们集成。这与“从零开始就在一起训练”的原生路径在理念上有本质区别。

Qwen3-VL 意识到了“组合式架构”的潜在弱点:如果只把视觉编码器的最终输出给 LLM,可能会丢失细节。(不同于以往将所有视觉 token 仅输入到 LLM 输入层的做法,Qwen3-VL 的 DeepStack 抽取视觉编码器(ViT)的中间层特征,并将其注入到 LLM 的特定层中。)

解决方案(DeepStack):它不只是“看”一眼(only input),而是把视觉编码器不同层级(从浅层纹理到深层语义)的特征都提取出来,注入到 LLM 的不同层中。 ▪ DeepStack 采用的是残差连接(Residual Connections)的方式。 ▪ 它将视觉特征直接“加”到 LLM 内部隐藏状态(Hidden States)上,而不是拼接到序列的开头或结尾。因此,对于 LLM 来说,处理的 Seq Length 保持不变。

视觉编码器

Qwen-VL:

  • 视觉编码器直接用的是 OpenCLIP ViT-bigG 初始化,属于比较典型的“拿一个现成强视觉塔接到 LLM 上”的路线。
  • 论文写得很明确:图像先被 resize 到固定分辨率,ViT 以 stride 14 切 patch,再输出视觉特征;随后通过一个单层、随机初始化的 cross-attention adapter,把整张图的视觉序列压到固定 256 个 token,再送进 LLM。

Qwen3-VL:

  • Qwen3-VL 的视觉编码器,不只是“把图变成 token”的普通 ViT,而是一个原生支持 image + video、动态分辨率、并且能导出多层视觉特征的视觉前端。
  • 重点有 4 个。
    • 它原生支持视频,不只是图片。 官方配置里有 temporal_patch_size=2,实现里整个视觉网格都按 grid_thw 处理,也就是 time / height / width 三维,而不是传统单图的二维网格。 这说明它的视觉编码器从一开始就是按“时空输入”设计的。
    • 它支持动态分辨率,不走“固定 256 视觉 token”那种老路。 官方 README 直接写了:Qwen3-VL 的单图视觉 token 预算可以在 256-1280 之间,并提到 compression ratio is 32。 配置里还有 spatial_merge_size=2,说明它会在视觉侧做空间合并来控制 token 数量。 这和 Qwen-VL 那种“adapter 固定压成 256 个 token”差别很大。
    • 它不是只用最后一层视觉特征,而是显式保留多层特征。 配置里有 deepstack_visual_indexes = [8, 16, 24];实现里会在这些层取特征,再经过额外的 deepstack_merger 输出。 这意味着视觉编码器不是“最后吐一层 hidden state 就完事”,而是把中层、深层信息都保留下来,供后面 DeepStack 融合。
    • 它的视觉位置建模也更复杂,适合变分辨率输入。 从实现看,视觉编码器里同时用了:
      • 基于网格的可插值 pos_embed
      • 基于 position_ids 的视觉 rotary embedding 这不是简单“固定尺寸图片 + 固定位置表”的做法,而是为了适配不同 T/H/W 网格。
  • README 里还写了 DeepStack 会融合多层 ViT 特征;视觉 token 也不是固定 256,而是按分辨率动态分配,单图 token 预算可在 256-1280 之间,视频还会有额外时间压缩。

多模态架构

Qwen-VL 的多模态架构特点:

  • 结构是三段式:LLM + Visual Encoder + Position-aware Adapter。
  • 视觉信息先被单独编码,再被 adapter 压成固定长度,最后以 … 形式插到语言序列里。
  • 它支持多图交错输入,但从架构本质上看,还是“先视觉编码,再注入语言模型”的老式桥接范式。
  • 位置建模主要是 adapter 里加了 2D absolute positional encoding,重点是别在压缩时把位置信息丢太多。

Qwen3-VL 的多模态架构特点:

  • 它把“交错多模态上下文”当成一等公民,原生支持 text + image + video 的统一长上下文,论文里给的是 256K。

kimivl

Kimi-VL 的 MLP(看新图中的 Vision Encoder -> MLP -> LLM 链路): 它是位于大语言模型(LLM)之外的。它扮演的是跨模态适配器(Connector)*的角色。 视觉编码器(Vision Encoder)提取出的图像特征维度(例如 1024 维),大语言模型根本无法直接识别。这个 MLP 的物理位置处于视觉模块与语言模块的交界处,其作用是进行*模态间的对齐与维度转换(将视觉空间几何映射到文本 Token 嵌入空间)。

image-20260528155205926

gemini

▪ 没有分阶段:Gemini不是先训练一个纯文本的LLM,再用多模态数据进行第二阶段的微调。而是从预训练的第一天开始,就在同时学习所有模态。

▪ 统一的Transformer核心:如图2所示,所有模态的输入(文本token、图像帧、音频特征)都被转换成一个统一的向量序列,然后被送入同一个Transformer解码器进行处理。

▪ 联合训练带来的深远影响 ○ 统一的内部表示空间 (Unified Representation Space):由于从头开始联合训练,Gemini的神经网络权重被迫去学习不同模态之间的深层关联。它不是为图像信息在已有的文本“世界观”里“找个位”,而是构建了一个统一的、可以同时容纳和关联视觉、听觉、语言概念的内部世界。 ○ 实现真正的跨模态推理 (Deep Cross-modal Reasoning):因为底层表示是统一的,Gemini可以进行更复杂的推理。

image-20260528153603649

对齐阶段

对齐阶段(Alignment Stage)是指在大语言模型(LLM)或多模态大模型(MLLM)的完整训练管线(Training Pipeline)中,紧随预训练(Pre-training)和监督微调(SFT)之后,旨在引导模型行为、使其输出符合人类意图、社会价值观、偏好及事实真实性(Factuality)的特定网络微调阶段。

对齐阶段在模型生命周期中的位置

1
2
1. 预训练阶段 (Pre-training) ──> 2. 监督微调阶段 (SFT) ──> 3. 对齐阶段 (Alignment)
(海量数据, 学会语言规律) (高质量问答, 学会听懂指令) (偏好数据, 学会安全与诚实)

预训练(Pre-training): 模型通过无监督学习,在数万亿 Token 上最大化下一个词的预测概率。此时模型是一个功能强大的“续写机器”,但其输出不可控,极易产生幻觉、脏话或危险言论。

监督微调(SFT, Supervised Fine-Tuning): 通过成千上万条高质量的“人工标准指令-回答”对,训练模型学会以“AI 助手”的身份规范回答问题。

对齐(Alignment): 在 SFT 之后,模型虽然会说人话了,但依然存在顽固的“肌肉记忆”(例如前面提到的语言先验偏置,导致闭眼瞎编)。对齐阶段则是通过引入“好”与“坏”的对比信号,进一步修剪模型的概率空间,约束其行为。

对齐阶段的核心对齐指标

在学术定义中(由 Anthropic 等机构确立),对齐阶段主要围绕以下三个维度(3Hs)进行行为约束:

  • Helpfulness(有用性): 模型的回答是否切中用户意图,不废话,具备实质性帮助。
  • Harmlessness(无害性): 模型是否拒绝回答涉及暴力、偏见、违法等敏感或危险的指令。
  • Honesty(诚实性/事实性): 这正是你目前阅读的抗幻觉论文的核心靶点。 诚实性要求模型“知之为知之,不知为不知”,在多模态场景下,必须严格忠实于输入的视觉上下文,不编造不存在的物体。

对齐阶段的数学与工程实现技术

对齐阶段的核心不是通过传统的“给正确答案(SFT)”来实现的,而是通过“偏好学习(Preference Learning)”。其主流技术路径有两条:

❶ 强化学习路径(基于奖励模型的在线对齐)

  • 代表技术: RLHF(基于人类反馈的强化学习)。
  • 机制: 训练一个独立的“奖励模型(Reward Model)”充当裁判,只要模型吐出的词包含幻觉或不安全信息,奖励模型就扣分;反之给高分。模型通过 PPO 算法更新参数,在奖惩中自适应地调整输出策略。

❷ 监督偏好优化路径(基于对比损失的离线对齐)

  • 代表技术: DPO(直接偏好优化)、DenseDPO。
  • 机制: 放弃强化学习环境,直接给模型喂入偏好对数据 𝒟 = {(x, ychosen, yrejected)}。通过最大化优选回答 ychosen 的条件概率、同时最小化弃选回答 yrejected 的条件概率,直接一步到位地调整模型权重。

PPO (Proximal Policy Optimization,近端策略优化)

【PPO算法】强化学习头牌,学大模型必懂_哔哩哔哩_bilibili

image-20260529125515081

PPO 是 OpenAI 在 2017 年提出的一种强化学习(RL)算法。在大模型火爆之前,它就已经在让 AI 玩 Dota 2、控制机械臂等领域名声大噪。在大模型对齐中,它的核心任务是:如何在保证大模型不“学崩”的前提下,尽可能地迎合裁判(奖励模型)的口味?

1. 为什么叫“近端”?它解决了什么痛点?

在 PPO 之前,强化学习最经典的算法是策略梯度(Policy Gradient)。它的逻辑很简单:AI 做出一个行为,如果得到好评,就通过梯度上升调高这个行为的概率。

但它有一个致命缺陷:步子迈得太大,容易扯到蛋。 强化学习是“边收集数据边学习”的(On-policy)。如果某一次参数更新幅度过大,导致模型的策略发生了剧烈变形(比如从“温和的助手”变成了“满嘴脏话的喷子”),那么它接下来收集到的交互数据就会全部变成垃圾。模型一旦掉进这个“毒化”的悬崖,就再也爬不回来了。

为了解决这个问题,PPO 强调 “Proximal(近端/邻近)”限制每次参数更新的幅度,确保新策略(更新后的模型)和旧策略(更新前的模型)拉开的距离不要太大。

2. PPO 的核心武器:截断目标函数 (Clipped Surrogate Objective)

PPO 实现“步子不迈太大”的方式非常简单粗暴且优雅,它在损失函数中引入了截断(Clipping)机制

首先,我们定义一个重要性采样比率(Probability Ratio)rt(θ),它表示新策略和旧策略输出某个动作的概率比值

$$r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$$

  • 如果 rt>1,说明新策略中这个动作的出现概率变大了。
  • 如果 rt<1,说明新策略中这个动作的出现概率变小了。

PPO 的目标损失函数如下:

$$L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \left[ \min\left(r_t(\theta)\hat{A}_t, \, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t\right) \right]$$

这里的 A^t 是优势函数(Advantage Function),用来衡量当前的动作是比平均表现更好(A>0)还是更差(A<0)。ϵ 是一个超参数(通常设为 0.1 或 0.2)。

我们可以通过下面的经典原理图来直观理解这个公式的运作逻辑:

image-20260529130338112

结合上图,这个公式的妙处在于“悲观主义剪裁”:

  • 当动作是好动作时 (A^t>0):模型想继续增大 rt。但只要 rt 超过了 1+ϵ(比如 1.2),clip 函数就会把好评给“卡死”在 1+ϵ。这意味着就算你再怎么讨好裁判,梯度也不会再更新了,防止模型用力过猛。
  • 当动作是坏动作时 (A^t<0):模型想减小 rt。如果 rt 跌破了 1−ϵ(比如 0.8),说明模型已经充分吸取了教训,大幅度降低了该动作的概率。此时通过 min 取值,允许继续惩罚那些比旧策略还要糟糕极多的行为(如左侧深跌区域),但在常规区间内限制波动。

RLHF (Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)

在基础的预训练(Pre-training)阶段,模型的核心任务只是预测下一个词。这导致它虽然博古通今,但非常“毒舌”、喜欢胡说八道(幻觉),或者给出无法直接执行的废话。

RLHF 的核心目的就是“驯服”大模型,通过引入人类的真实反馈,建立一套奖惩机制,让模型的回答符合人类的价值观和使用习惯,即满足 3H 原则

  • Helpful(有用性)
  • Honest(诚实性)
  • Harmless(无害性)

RLHF 的三大核心步骤

经典的 RLHF 流程由 OpenAI 确立,是一个环环相扣的三阶段流水线

image-20260529130627194

1.步骤 1:监督微调 (SFT):基础建设。

挑选高质量的“提示词-标准回答”对(通常由专业人类写手撰写),让预训练模型进行模仿训练。这一步是为了让模型脱离“续写模式”,学会像一个AI助手一样去组织语言和回答问题。此时的模型被称为 SFT 模型

2.步骤 2:训练奖励模型 (RM):培养裁判。

让 SFT 模型针对同一个提示词生成多个不同的回答(比如 A 和 B)。然后让人类标注员对这些回答进行排序或打分(例如:A 比 B 更好)。

利用这些偏好数据,训练一个奖励模型(Reward Model)。这个模型的目标是像一个裁判一样,输入任意一个回答,就能自动输出一个质量得分。

3.步骤 3:强化学习优化 (PPO):闭环演练。

这是最核心、也是最复杂的阶段。让初始模型作为一个“演员”(Actor 策略模型),不断接收新的提示词并生成回答。

将回答送入上一步训练好的奖励模型打分。强化学习算法(通常是 PPO)根据分数的高低来更新 Actor 模型的参数——拿高分的行为被强化,拿低分的行为被抑制。

繁华背后的代价:RLHF 的痛点

虽然 RLHF 效果惊人,但它在工程上是一场“灾难”:

  • 资源无底洞:在步骤 3 训练时,显存里要同时挂载 4 个模型:Actor 模型(正在训练的)、Reference 模型(冻结的,用来算 KL 散度约束,防止模型彻底放飞自我)、Reward 模型(打分的裁判)和 Critic 模型(强化学习估算价值的)。
  • 奖励作弊(Reward Hacking):模型在强化学习过程中极其狡猾。它如果发现某种特定的句式(比如句尾多说谢谢,或者故意迎合人类的偏见)能让裁判模型打高分,它就会疯狂刷这种句式,导致回答的真实质量反而下降。

正是因为步骤 3 的 PPO 框架过于臃肿、极其难调(超参数极度敏感),业界才迫切需要一种更简洁的方案。这就直接催生了上一轮为你介绍的 DPO(直接偏好优化)——直接把步骤 2 和步骤 3 合二为一,用纯数学推导干掉了显式的强化学习循环。

DPO (Direct Preference Optimization, 直接偏好优化)

【DPO 算法】大模型主流训练方法,大部分人可能并没有真正弄懂…_哔哩哔哩_bilibili

1. 为什么需要 DPO?(痛点所在)

在 DPO 出现之前,主流的对齐方法是 OpenAI 走通的经典 RLHF(基于人类反馈的强化学习),通常采用 PPO (Proximal Policy Optimization) 算法。

PPO 的致命缺点就是:太难调了。 它同时需要维护 4 个模型(Actor, Critic, Reference, Reward),显存开销巨大;而且强化学习在离散的文本空间中极不稳定,超参数稍有变动就会导致训练崩溃。

2. DPO 的核心思路:数学上的“降维打击”

DPO 的伟大之处在于,它通过严谨的数学推导,证明了奖励模型(Reward Model)可以被策略模型(Policy Model)本身的条件概率完全表示出来

既然奖励函数可以隐式地包含在策略模型里,那为什么还要单独训练一个裁判模型呢?DPO 直接绕过了“训练奖励模型”和“强化学习”这两个最痛苦的步骤,将整个管线缩短:

DPO 的训练管线(对比传统 RLHF 移除了显式的奖励模型和 PPO 循环)

如上图所示,在经过基础的 SFT 得到参考模型πref 后,DPO 直接输入三元组偏好数据 (x, yw, yl) 进行优化:

  • x:提示词(Prompt)
  • yw:人类更偏好的回答(Chosen / Winner)
  • yl:人类不喜欢的回答(Rejected / Loser)

DPO 的优缺点对比

目前,Llama 3、Mistral 等绝大多数开源顶尖大模型的对齐阶段,都有 DPO 或其变体(如 IPO、KTO)的身影。

特性 优势 局限性
工程实现 极度简单。不需要复杂的强化学习框架,直接写个标准的监督训练 Loss 就能跑。 容易过拟合。由于缺少 PPO 的在线探索,DPO 极度依赖离线数据的质量,容易把偏好数据榨得太干导致泛化性变差。
资源消耗 省显存。训练时只需要常驻当前模型 πθ 和参考模型 πref(甚至可以用合并或冻结技术进一步压缩),不需要多余的 Reward 和 Critic 模型。 对噪声敏感。如果标注数据里有混淆(比如把坏回答误标成好回答),DPO 强行拉大差距会导致模型概率分布扭曲。
训练稳定性 极其稳定。本质是凸优化,不会出现 PPO 那种训练到一半指标突然崩塌、无法收敛的情况。 缺乏分布外探索。它只能在给定的数据范围内做选择,无法像在线 RL 那样通过“探索新文本”发现更好的生成策略。

介绍

2605.27993 重新思考视觉忽视:通过情境引导——MLLM幻觉缓解的偏好

本文为这篇论文的阅读学习笔记

挑战以往工作中“增强视觉依赖即可降低幻觉”的单一假设,通过系统性干预实验发现,图像会同时与模型参数化知识和历史文本上下文产生竞争关系。基于这一观察,提出训练无关的 Context-Preference Activation Steering(CAS)推理期干预方法,将 MLLM 的视觉依赖解耦为两类上下文偏好。

设计两组冲突样本来提取 Context Preference Vectors:基于反事实图像构造 Visual Fidelity Vector(VFV),刻画模型在“外部视觉证据 vs 内部常识先验”之间的偏好;基于对称图文冲突对构造 Modality Reliance Vector(MRV),刻画模型在“视觉上下文 vs 文本上下文”之间的模态依赖。

在 LLaVA-1.5、Qwen-VL、Shikra 等主流 MLLM 上的实验表明,不同模型对视觉信息、参数化知识和文本上下文的依赖存在显著异质性,并提出带符号的激活引导干预:在推理阶段根据模型自身的偏好方向选择正向或负向干预,在 MLP 层输出处进行带符号的残差注入。

实验结果显示,CAS在多个模型评测中,平均 CHAIR_S 从 55.5 降至 38.5,下降 30.7%,在显著降低物体幻觉的同时保持生成质量稳定。在 AMBER 生成式评测中,平均 Hal 从 36.1 降至 25.9,下降 28.2%,验证了 CAS 在多模型、多基准任务上的稳定泛化能力。推理延迟为 28.09ms/token,与原模型基本同量级,几乎不引入额外推理开销。

幻觉现象

1.强大的“语言刻板印象”(Language Prior / Statistical Bias)

大模型在纯文本预训练阶段,读了上万亿字的人类书籍。在它的记忆里,某些词是高度绑定、经常一起出现的(即 Co-occurrence 统计偏见)。

大模型的参数量通常比视觉部分大得多,会出现即使视觉部分看清了,但是还是被语言部分影响,因此出现幻觉

2.长文本中的文本惯性或过度信赖(Text Inertia / Over-trust)

前者指多模态模型在自回归解码中,由于文本特征流的权重压倒了视觉特征流,导致后续 Token 的生成完全受历史文本指令支配,模型输出退化为无条件语言建模(Unconditional Language Modeling)的行为。

后者指在 Transformer 的自回归解码阶段,自注意力矩阵(Self-Attention Matrix)的权重在已生成的文本 Token 上形成了异常密集的局部聚集,形成“注意力黑洞”,从而阻断了来自图像(Vision Tokens)的信息流。

评测指标

CHAIR 评测流程:基于文本解析的“名词物体验证”

CHAIR 的评测本质是一个“生成-提取-比对”的流水线,通常建立在 MSCOCO 数据集上(因为该数据集有极度详尽的人工物体标注)。

1. 模型生成(Caption Generation)

  • 给大模型输入一张测试图片,并配合标准的生成提示词(例如:"Describe this image in detail.")。
  • 模型自回归生成一段完整的描述性文本(Caption)。

2. 文本解析与名词提取(POS Tagging & Parsing)

  • 评测脚本调用自然语言处理工具包(如 SpaCy 或 NLTK)对模型生成的文本进行词性标注(POS Tagging)
  • 过滤并提取出所有的名词和名词短语(例如:“cat”、“table”、“apple”),这些代表模型认为图片里存在的物体。

3. 同义词与类别映射(Synonym Mapping)

  • 大模型输出的词汇可能五花八门(如 “kitty”),而 MSCOCO 固定的物体标签只有 80 类(如 “cat”)。
  • 评测系统使用预设的同义词词典,将模型生成的名词全部归一化映射到官方的物体类别中。

4. 真实标签比对(Ground-Truth Matching)

  • 将映射后的物体名词,与该图片在数据集里自带的人工标注物体列表(Ground-Truth Objects)进行逐一比对:
    • 如果模型说了 “dog”,而人工标注里有 “dog”,则计为正确物体
    • 如果模型说了 “sofa”,但人工标注里完全没有 “sofa”,则计为幻觉物体(Hallucinated Object)

5. 指标结算

根据全量测试集的结果,带入公式计算:

  • CHAIRI (Instance-level)$\frac{\text{幻觉物体词出现的总次数}}{\text{所有生成的物体词总次数}}$
  • CHAIRS (Sentence-level)$\frac{\text{包含至少一个幻觉物体的句子数量}}{\text{总生成句子数量}}$

POPE 评测流程:基于非对称采样的“是非题盲测”

POPE 彻底抛弃了自由文本生成的复杂性,直接将幻觉转变为二分类(Yes/No)*的问答流程。其核心灵魂在于*如何向模型提问(即负样本采样策略)

1. 构建智能问答库(Question Construction)

评测集针对每一张图片,构建大量的“图片中是否有 [物体] ?”的标准问题。问题分为两大家族:

  • 正样本问题(表中确实有):根据 Ground-Truth,直接问图片里存在的物体(预期回答:Yes)。
  • 负样本问题(表中其实没有,专门用来钓鱼):这是 POPE 的核心,分为三种模式:
    • Random(随机):从整个数据集的物体库中,随机挑一个这张图没有的物体(如:图里是厨房,随机问有没有“斑马”)。太简单,模型容易识破。
    • Popular(高频):挑选整个数据集里出现频次最高的物体(如“人”、“包”),哪怕这张图里没有。考验模型是否对高频词有盲目偏见。
    • Adversarial(对抗式):根据协同过滤原理,挑选那些经常与图中物体共同出现、但这张图里恰好没有的物体(如:图里有“马路、汽车”,偏问有没有“红绿灯”)。极具误导性,最容易诱发幻觉。

2. 模型推理(Inference)

  • 将(图片 + 每一个 Yes/No 问题)成对喂给模型。
  • 强制模型输出,或者提取模型输出的第一个有效 Token(锁定为 "Yes""No")。

3. 混淆矩阵判定(Confusion Matrix)

将模型的回答与真实答案对比,塞进经典的二分类评测矩阵:

  • 图有且模型说 Yes True Positive (TP)
  • 图无但模型说 Yes False Positive (FP) 【判定为幻觉】
  • 图有但模型说 No False Negative (FN)
  • 图无且模型说 No True Negative (TN)

4. 指标结算

计算全集下的 Accuracy(准确率)Precision(精确率)Recall(召回率) 以及 F1-Score。如果 F1 和 Precision 很低,说明模型是个“Yes Man”(不管看到啥都只会盲目说 Yes)。

AMBER 评测流程:无需大模型的“双轨交织矩阵”

AMBER 结合了 CHAIR 的自由表达验证和 POPE 的严苛抗噪能力,并且实现了多维度的纯自动化流水线。

1. 数据分流:启动双轨测试(Dual-Path Input)

同一张图片进入 AMBER 评测集后,会被同时分流到两个完全不同的测试轨道:

轨道 A:AMBER-G(生成式轨道)

  • 流程:向模型发送开放式指令(如描述图像)。
  • 提取:模型吐出长文本。AMBER 并不调用 GPT-4 这种昂贵的外部裁判,而是使用内置的经过优化的自动化关键词匹配正则引擎
  • 比对:提取出名词后,直接与图片对应集成的 Object 字典匹配。
  • 结算:输出 CHAIRCover(模型提及了多少比例的真实物体) 以及 Hal(整体句级幻觉率)

轨道 B:AMBER-D(判别式轨道)

  • 流程:向模型发送定制的是非题(Yes/No),但这些问题被精细地打上了维度标签
  • 多维度设陷
    • 存在性问答:“图里有xx吗?”(验证模型是否凭空造物)
    • 属性问答:“图里的那辆车是红色的吗?”(实际是蓝色的,验证状态幻觉)/“图里有三只猫吗?”(实际只有一只,验证数量幻觉)
    • 关系问答:“笔记本电脑是在桌子的下面吗?”(实际在上面,验证空间关系幻觉)
  • 结算:模型回答 Yes/No 后,系统分别计算出存在、属性、关系三大独立维度的 Accuracy 和 F1-score。

2. 全局综合得分汇算(Final Aggregation)

AMBER 会把生成式轨道和判别式轨道的得分加权汇总,输出一份最终的综合雷达图评分

这套双轨流程确保了:任何通过“牺牲生成能力、只一味回答 No”来刷判别式高分的作弊手段,都会在生成式轨道的 Cover 指标上暴雷;反之,胡言乱语的模型也会在判别式轨道上大比分落后。这也是为什么前沿工作非常喜欢用它来做最终全方位验证的原因。

解决的主要路线

通常,要解决大模型的幻觉或错误,有两种主要路线:

  1. 训练阶段方法(Training-time methods): 重新收集高质量数据,对模型进行对齐训练(如结合人类反馈的强化学习 RLHF、监督微调 SFT)。缺点: 耗费巨大的计算资源、时间和金钱,且一旦模型训练完,固有的错误很难再改。
  2. 推理阶段方法(Inference-time methods): 不需要重新训练或微调模型(Training-free),直接在模型吐出字(Token)的过程中进行实时干预。优点: 成本极低(甚至接近零额外成本),即插即用,非常灵活。

相关工作

基于训练的幻觉缓解 (Training-based Hallucination Mitigation)

核心方法与代表性技术

根据目前的研究进展,基于训练的缓解方案主要演化出以下几种代表性路径:

  • 基于 DPO 的基础对齐:这类方法通常在忠实(faithful)的响应与包含幻觉(hallucinated)的响应之间构建显式的偏好关系,通过直接偏好优化(DPO)来让模型学会拒绝幻觉。
  • 感知增强的 DPO 变体:近期的研究进一步将“幻觉感知的偏好构造”与“多模态感知先验”纳入其中,专门面向物体感知和感知增强进行扩展。
  • Re-Align (Xing et al., 2025):该方法通过图像检索技术来构造双重的“文本-视觉偏好对”,并使用检索增强的 DPO 技术对视觉语言模型(VLM)进行微调。
  • RLHF-V (Yu et al., 2024):将人类反馈强化学习(RLHF)的思想引入多模态幻觉抑制中。它通过收集片段级(segment-level)的细粒度纠错反馈,并利用 Dense DPO 进行精准的行为对齐。

该路线的优势与局限性

虽然基于训练的方法能够为模型带来稳定的收益,但它在实际落地时也存在明显的瓶颈:

优势 (Pros) 局限与挑战 (Cons)
稳定性高:能够带来相对持久和稳定的幻觉抑制收益。 资源消耗高昂:极度依赖大规模的偏好数据以及大量的计算训练资源。
针对性强:可以通过引入细粒度的片段纠错或检索增强,提升对特定物体的感知精确度。 通用能力受损:对齐后的模型在通用任务上的表现,有时反而会弱于原始的 VLM 基座。
行为退化风险:标准的 RLHF 采用粗粒度偏好,容易引发“奖励黑客”(reward hacking)和“行为退化”(behavior degeneration)。

Training-free

先检测后修正的流程Detect-then-Correct Workflows

根据您提供的文献资料,以下为您详细介绍无需训练的“先检测后修正”流程(Training-free Detect-then-Correct Workflows):

核心逻辑与工作原理

这类方法并不直接干预模型的单次解码生成过程,而是构建一个后处理流程(Post-processing Pipeline)。它们会对大模型已经生成的原始文本结果进行多阶段的审查与重写,通过定位并纠正其中的错误,从而达到抑制物体幻觉的目的。

代表性技术与方法

根据文献,这类工作主要有以下几种典型的技术实现:

  • LURE (Zhou et al., 2024):训练一个专门的“幻觉修订器”。该修订器以模型生成的原始描述作为输入,在文本中精准定位幻觉物体,并将其重写为与视觉内容忠实的描述。
  • Woodpecker (Yin et al., 2024):将整个检测与修正过程标准化地拆分为五个阶段。它在运行中需要依次调用外部的 VLM 来分别完成“关键概念抽取”、“视觉声明验证”以及“文本重写”等子任务。
  • Generate-then-verify 变体 (Wu et al., 2025):这类近期的方法引入了回溯验证(Backtracking Verification)和重采样(Resampling)机制,通过在生成后进行验证循环,来减少缺乏图像证据支撑的文本生成。

该路线的优缺点分析

优势 (Pros) 局限与挑战 (Cons)
即插即用 (Plug-and-play):不需要修改或重新训练多模态基准模型的原生参数,具有很强的通用适配性。 推理延迟显著 (High Latency):由于采用流水线设计,每个样本的深度检查通常需要 3–5 次顺序的模型调用,无法满足流式实时输出的需求。
计算开销高昂 (High Overhead):这类方法高度依赖外部的 VLM、检测器或密集的重采样过程,带来了不可忽视的额外计算资源消耗。

回溯与层对比解码 (Retrospection and Layer-Contrastive Decoding)

1. 核心假设与原理

这类方法建立在一个共同的隐含假设之上:模型在生成文本时,其内部其实已经隐式地编码了关于“相对忠实度”(Relative Faithfulness)的信号。因此,不需要修改模型参数,也不需要调用外部工具,只需在解码(Decoding)阶段设计特定的机制去检测并放大这些正面信号,就能在输出端抑制幻觉。

2. 代表性方法与技术实现

根据技术路径的不同,该路线主要衍生出“回溯机制”与“跨层对比”两种方案:

  • 回溯机制(以 OPERA 为代表)
    • 工作原理:OPERA (Huang et al., 2024) 在模型的束搜索(Beam Search)过程中,会实时监测注意力权重的分布。
    • 关键操作:一旦在某些 token 上检测到“过度信任”(Over-trust)的异常注意力模式(这通常是幻觉累积的信号),就会触发回溯重新选择机制,强行让模型退回上一步并重新选择更可靠的 token。
  • 层对比解码(以 DoLa 和 DeCo 为代表)
    • 工作原理:DoLa (Chuang et al., 2024) 及其在多模态领域的扩展工作 DeCo (Wang et al., 2025a) 试图在模型的层级(Layer Level)上捕捉忠实度信号。
    • 关键操作:它们会将模型较高层和较低层的隐层状态同时通过语言头投影到词表上,计算各自的输出 Logits,并让二者进行对比(Contrast)
    • 筛选策略:在解码时,模型会优先保留那些在跨层对比中“一致性更高”的 token,从而过滤掉低层中残留的幻觉噪声。

3. 该路线的局限性与痛点

虽然这类方法善于发掘模型的“内在潜能”,但在实际流式部署中存在以下技术瓶颈:

  • 高昂的推理开销,破坏流式输出:回溯方法(如 OPERA)需要频繁维护和执行多次“束回退”(Beam Fallbacks),这会导致模型生成的文本无法像常规大模型那样顺畅地流式逐字输出,并带来了明显的额外推理延迟。
  • 加剧重复退化(Repetitive Degeneration):层对比解码类方法在放大特定跨层一致性信号的同时,往往会破坏语言模型的自然流利度,极其容易引发文本的死循环和无意义重复
  • 对调参极其敏感:这类方法的效果高度依赖于层选择(Layer Selection)。在不同的基座模型上,究竟对比哪几层才能精准抑制幻觉,需要进行极其繁琐且敏感的超参数微调。

基于视觉忽略假设的方法(Methods under the Visual-Neglect Hypothesis)

核心假设

这一类工作建立在一个共同的隐含共识之上:多模态大模型(MLLM)的物体幻觉主要源于模型的“视觉忽略”(Visual Neglect)以及对文本先验的过度依赖。因此,无论是哪种具体的基座模型,这些方法都会通过不同的推理期干预手段,促使模型“把更多注意力放在图像上”(Pay more attention to the image),以此来抑制幻觉。

四种代表性技术实现

在具体的技术路径上,这四种方法分别在模型的解码端(Decoding side)前向传播端(Forward-pass side)展开了不同的干预:

1. VCD (Visual Contrastive Decoding)
  • 干预位置:解码端(Decoding side)。
  • 技术核心:在推理时,对比原始图像与高斯噪声图像所对应的输出 Logits。
  • 主要目的:通过两者的差值对比,强行抑制在视觉信号退化(加入高斯噪声)后,模型内部依然残留的盲目语言先验。
2. PAI (Paying More Attention to Image)
  • 干预位置:前向传播端(Forward-pass side)。
  • 技术核心:直接强行放大图像 Token 在计算时的自注意力权重。
  • 辅助手段:在生成过程中,会进一步结合来自纯文本输入(无图输入)的对比 Logits,以此来削弱语言偏见。
3. AttnReal (Attention Reallocation)
  • 干预位置:前向传播端(Forward-pass side)。
  • 技术核心:实施注意力重分配机制。
  • 具体操作:随着文本越写越长,模型往往会在历史生成的 Token 上过度累积注意力;AttnReal 会实时检测这一现象,并将这些被文本截留的注意力额度重新分配给视觉 Token,强迫模型回头看图。
4. SSL (Steering LVLMs via Sparse Autoencoder)
  • 干预位置:前向传播端(Forward-pass side)/ 激活导向。
  • 技术核心:引入预训练的稀疏自编码器(Sparse Autoencoder, SAE)
  • 具体操作:在模型的隐层中,将预先定义和提取好的“忠实特征向量”和“幻觉特征向量”,直接施加(Steer/注入)到对应的视觉 Token 与输出 Token 上,从认知底层的概念向量层面完成纠偏。
该假设与方案的局限性

虽然上述方法在特定场景下取得了一定成功,但本文作者通过系统性的重新审视,指出了“视觉忽略假设”存在的三大致命局限

  1. 完全忽略了模型的异质性:“只要放大视觉信号就足以降低幻觉”的假设是不完整的。作者通过实验发现,不同基座模型对视觉偏好的响应具有强烈的异质性;对某些模型而言,强行推动其更强地依赖视觉信息反而可能加剧幻觉
  2. 易引发重复退化:由于这类方法属于硬性增强或强力抑制,往往会破坏模型原生的生成流形(Generation Manifold),导致模型在生成文本时极易陷入死循环,引发重复退化(Repetitive Degeneration)问题。
  3. 提取成本高昂:以 SSL 为代表的激活导向方法,其导向信号(特征向量)的提取过程往往高度依赖于外部模型或繁重的预训练资源,无法做到真正的轻量自适应。

pai与AttnReal 缺点

“在推理阶段通过修改注意力矩阵来放大视觉词元(Image Tokens)权重”的方法(例如 PAI、AttnReal 等算法),无法直接应用在原生多模态模型中,而且这样做不仅没有必要,反而会导致模型崩溃。

1. 物理边界消失,无法在自注意力矩阵中精准定位

在 LLaVA 等拼接多模态模型中,图像由独立的视觉编码器处理,生成的“图像 Token”与提示词的“文本 Token”在序列中的位置和界限是非常清晰的。PAI 或 AttnReal 算法正是利用这一点,直接去自注意力矩阵(Self-Attention Matrix)中锁定属于图像 Token 的行和列,然后进行乘法放大。

然而,在原生多模态模型中,图片和文本在最底层通过统一分词器(Unified Tokenizer)被转化为同等地位的交织词元(Interleaved Tokens)。进入统一的 Transformer 主干网络后,各模态的特征空间在计算图内部高度融合,模态之间的物理边界已经模糊。在注意力矩阵中,你无法画出一个干净的边界去单独剥离并放大所谓的“视觉部分”。

2. 强行干预会破坏联合概率分布,导致语义崩溃(输出乱码)

原生多模态模型(如 GPT-4o、Gemini)的参数在训练时,已经让文本、图像等多种模态达到了联合概率分布的最优均衡。

如果你在推理阶段,强行对自注意力层中某些你认为是图像位置的词元施加巨大的数学惩罚或奖励,这种粗暴的外力干预会立刻破坏模型长序列建模的上下文连贯性。其后果不是缓解幻觉,而是直接破坏模型的解码逻辑,导致模型退化,直接输出乱码或产生逻辑断层(即语义崩溃)

核心动机(Motivation)与核心实验发现(Findings)

❶ External-vs-Internal 竞争

  • 本质: 实时输入的外部视觉条件约束(External Visual Context)与模型参数权重中固化的内部先验知识(Internal Parametric Knowledge)之间的抗衡。
  • 现象: 模型眼前面临一张“蓝色香蕉”的图,其体内的参数化知识却强烈倾向于预测“黄色”。

❷ Cross-Modal 竞争

  • 本质: 视觉上下文(Visual Context)历史文本上下文(Textual Context)之间的支配权拉扯。
  • 现象: 提示词文本中包含强烈的诱导性或错误历史(如“前文说树上有一个橘子”),导致模型在生成后续 Token 时,注意力矩阵的能量偏向文本端而忽略视觉端(即退化为无条件文本建模)。

上下文偏好向量(CPVs)的解耦与提取

为了定量化表征上述两种竞争在隐空间(Latent Space)中所处的多维几何方向,论文设计了两种冲突激发实验(Conflict-based Probing),从而在不改动模型参数的前提下,逆向提取出两个上下文偏好向量(Context Preference Vectors, CPVs)

❶ 视觉保真向量(Visual Fidelity Vector, VVFV

  • 提取手段: 利用 反事实图片(Counterfactual Images)
  • 机理: 强行制造外部视觉与内部常识的极端冲突。捕获模型在此冲突下的隐层激活状态(Hidden States),通过差分计算剥离出纯粹掌管“外部真实视觉信任度”的特征方向。

❷ 模态依赖向量(Modality Reliance Vector, VMRV

  • 提取手段: 利用 对称图文冲突对(Symmetric Image-Text Conflict Pairs)
  • 机理: 固定视觉输入,在文本端施加偏置(或反之)。通过测量模型在多模态对齐平衡被破坏时的空间轨迹,提取出控制“该听图的还是该听文的”模态天平方向。
image-20260603081542984

提取上下文偏好向量

论文指出,图像作为一种多模态上下文,在模型内部其实面临着双重语义竞争:一是与模型预训练获得的参数化知识竞争;二是与指令和生成历史构成的文本上下文竞争。基于这一全新视角,CAS 框架将视觉偏好解耦为两个独立的轴,并在离线(Offline)阶段通过精妙的样本设计来提取两个上下文偏好向量(Context Preference Vectors, CPVs)

以下为您梳理其核心的构造与提取机制:

一、 视觉保真向量(VFV):解决“外部视觉”与“内部常识”的冲突

  • 概念定位:捕获的是 external-vs-internal 的竞争偏好,即模型在面对外界看到的图像与内心固有的常识发生冲突时,究竟选择相信谁。
  • 构造方法(设定 I:反事实图像构造)
    • CAS 构造了一组违反常识的反事实图像(Counterfactual Images)
    • 每个冲突样本的数学形式为:(Icf,q,y+,y)
    • 结合图2的经典示例
      • Icf(反事实图像):一根被整条涂成蓝色的香蕉图片。
      • q(提示词问题):“What color is the banana in this image? Answer with one word.”(图像中的香蕉是什么颜色?用一个词回答。)
      • y+(忠于图像的正确答案):blue(蓝色)。
      • y(依赖常识的幻觉答案):yellow(黄色,因为常识中香蕉是黄色的)。
  • 提取原理:当这个样本输入给多模态大模型时,模型在 y+(视觉)和 y(常识)之间的概率倾斜和最终选择,直接反映了它在“外部视觉上下文”与“内部参数化知识”之间的相对偏好。通过捕获模型在这一冲突下的隐层激活状态,即可提取出 视觉保真向量(Visual Fidelity Vector, VFV)

二、 模态依赖向量(MRV):解决“视觉上下文”与“文本上下文”的冲突

  • 概念定位:捕获的是 cross-modal(跨模态)的竞争偏好,即当“眼睛看到的”和“耳朵听到的(前文提示)”发生矛盾时,模型更依赖哪种模态。
  • 构造方法(设定 II:对称图文冲突对)
    • 首先选取一对权重相当但语义不同的候选答案 (A, B),例如(apple vs. orange)。
    • 为了彻底消除模型对某个具体词汇的固有偏好(即排除参数化知识的干扰),CAS 采用了极其巧妙的对称设计,将样本拆分为两个子集:
      • 子集 Sa{(IA, qB, y+ = A, y = B)}
        • 示例:图像里是一棵结满苹果的树(IA),但文本提示词(qB)里故意误导它说:“之前的描述说树上长了橘子。请问这棵树上长了什么水果?” 此时,听眼睛的答案 y+apple,顺从文本误导的答案 yorange
      • 子集 Sb{(IB, qA, y+ = B, y = A)}
        • 示例:把图文完全反过来。图像里是橘子树IB),文本提示词(qA)里却睁眼说瞎话:“之前的描述说树上长了苹果。请问这棵树上长了什么水果?” 此时偏向视觉的答案变成了 orange,偏向文本的答案变成了 apple
  • 提取原理(为什么要对称设计?)
    • 在普通的文本生成中,不同 Token 在词表里的原生出现频率是不一样的,这会带来严重的词表频率噪声。
    • 而在 CAS 的对称设计中,偏向视觉证据的答案和偏向文本先验的答案在 SaSb 之间完全翻转
    • 当把这两部分数据联合起来让模型进行拟合时,与特定 token 频率相关的偏差在数学上会相互抵消。这确保了最终提取出的 模态依赖向量(Modality Reliance Vector, MRV) 捕获的是纯粹的跨模态偏好,而不会夹杂任何词表噪声。

如何提取CPVs

1. 为什么选择 MLP 层作为提取位置?

在线下提取偏好向量时,无论是针对“反事实图像(VFV)”还是“对称图文冲突(MRV)”的样本设定,它们都共享完全相同的提取流程 。 作者团队选择 Transformer 中的 MLP(Feed-Forward)层作为提取和注入的基点 。这背后的理论支撑在于:现有的前沿研究已经证实,MLP 层是大模型中存储事实知识的关键模块,并且对模型的最终输出起到了因果决定性作用

2. 定位关键 Token 位置(t

为了捕获模型在面对冲突(例如:视觉看到的与文本暗示的不一致)时的内心真实“纠结”状态,提取算法进行了精细的时间步定位:

  • 模型首先自回归地生成答案 。
  • 算法会定位到与视觉对齐的答案(y+)或与常识/文本对齐的答案(y)在序列中最早出现的位置,并将其首个 Token 的位置标记为 t

3. 计算连续偏好信号(pref

在定位到 t 之后,算法会回退一步,在前一个位置 t − 1 处读取模型预测下一个 Token 的概率对数(Logits),记为 。接着,通过以下公式定义一个连续的偏好信号 pref

pref = maxi ∈ T+i − maxj ∈ Tj

T+:代表与视觉上下文一致的答案 y+ 的首 Token 候选集(包含各种大小写和空格变体) 。

T:代表与对立上下文(参数化知识或文本上下文)一致的答案 y 的首 Token 候选集 。

物理意义pref 的数值正负和大小,直接反映了模型在这一步更偏向于信任视觉(正值)还是更偏向于信任先验/文本(负值) 。

T+T随着数据集的构建,针对每一个样本提前定义好的候选 Token 集合

当你设计好一个样本(比如:图片是绿草莓,问题是“什么颜色”)时,你就已经明确知道了这个样本的 y+ 是“绿色”,y 是“红色”。

只要Token 第一次掉进了 T+ 或者 T 的名单里,便可以开始计算pref

这一个 logits 向量里,包含了词表里所有 Token 的得分,它们绝对是在同一个 logits 向量里抽取的

4. 逐层岭回归(Ridge Regression)拟合

同样在 t − 1 这一位置,算法会导出每一层 l 的 MLP 输出隐藏状态 hl 。为了把隐藏状态(高维空间向量)和模型的行为表现(偏好信号 pref)建立映射,作者采用了 2 正则化的岭回归进行闭式求解 :

minwl, bls(prefs − wlhl(s) − bl)2 + λwl22

在实际拟合前,为了保证高信噪比,算法会统一过滤掉行为信号过弱的样本(即 |pref|[citestart] < ϵ,数据过小说明模型本身没形成明显偏好) 。

最终,每层独立求解得到的权重向量 wl 就是我们需要的上下文偏好向量(CPV)

  • 在设定 I(反事实图像)上拟合得到的权重,称为 VFV(视觉保真向量)
  • 在设定 II(对称图文冲突对 Sa ∪ Sb)上联合拟合得到的权重,称为 MRV(模态依赖向量)

在 MLP 的某一层,我们把海量样本的隐状态 h 聚在一起。利用岭回归,我们求解出一个最优的方向向量 w(这个 w 就是该层专属的 CPV)。它满足:任何一个样本的 h 只要与 w 做点积(即 h 投影到 w 方向上的长度),其结果都最接近该样本真实表现出的偏好行为特征 pref

5. 层定位(Layer Localization)

最后,关于“到底在模型的哪一层进行干预最有效”的问题,作者通过逐层归因实验进行了定位 :

最终选定区域:模型的中前部 MLP 层,即 L11–14 这一层带

原因分析:根据后续的层消融实验(表 4),中前层 L11–14 是唯一既能显著缓解幻觉、表现又十分稳定的干预窗口 ;如果选在更浅的层(如 L1–9)会导致模型严重的文本生成退化和失控重复,而选在更深的层(如 L16 之后)干预则基本失效,回到原生(Vanilla)模型的水平 。因此,CPV 的提取和线上推理时的残差注入,都固定在 L11–14 层完成 。

计算pref的含义是什么

一、 什么是“读取模型预测下一个 Token 的概率对数(Logits)”?

在自回归大模型中,当输入前一个位置 t − 1 的内容后,模型的最后一层(Linear 头)会给词表里的每一个词(Token)都打一个原始分数。

  • 这个原始分数向量,就叫做 Logits(记为 。它还没有经过 Softmax 归一化,所以不是 0 到 1 之间的概率,而是有正有负的实数。
  • 分数越高,代表模型内心越想在下一步吐出这个词。

假设模型的词表里有几万个词,在 t − 1 这一步,模型输出的 Logits 分数向量 看起来可能长这样:

词表里的 Token (k) 原始分数 Logit (ℓk) 含义
"黄" 12.5 视觉派(偏向 y+
"Yellow" 11.8 视觉派(偏向 y+ 的英文变体)
"红" 14.2 偏见派(偏向 y
"的" 3.1 酱油角色
"苹果" -5.2 无关词

二、 为什么有 T+T,还要取 max 

因为在实际的 Tokenizer(分词器)中,同一个意思的词可能会被切成不同的 Token。比如“黄色”,模型可能想说中文的"黄",也可能想说英文的"Yellow"或者带空格的" Yellow"

为了公平起见,作者设立了两个“阵营”:

  • T+(视觉阵营候选集):所有能代表正确视觉答案开头的 Token,比如 ["黄", "Yellow", "yellow"]
  • T(文本/常识偏见阵营候选集):所有能代表常识错误答案开头的 Token,比如 ["红", "Red", "red"]

公式里的 maxi ∈ T+i 的意思就是:从视觉阵营里,挑出一个大模型最想说的词(分数最高的那个)。

在上面的表格中,视觉阵营里 "黄" 的分数最高(12.5),所以 maxi ∈ T+i = 12.5

同理,偏见阵营里 "红" 的分数最高(14.2),所以 maxj ∈ Tj = 14.2

三、 这个 pref 为什么这么算?它的物理意义是什么?

现在把两个阵营的“最强战力”拉出来作差,就得到了连续偏好信号 pref

pref = 视觉阵营最高分 − 偏见阵营最高分

带入上面的例子:pref = 12.5 − 14.2 = −1.7

这个减法做完后,得到的 pref 数值具有非常完美的物理意义,能够定量反映大模型在这一时刻的内心偏向和纠结程度

  • pref > 0 时(正数):说明视觉阵营胜出。分数值越大,说明模型越坚信自己眼睛看到的(视觉上下文),受常识或文本的干扰越小。
  • pref < 0 时(负数):说明偏见阵营胜出。分数值越负(比如 −5.0),说明模型越迷失在文本偏见或常识里,睁眼说瞎话。
  • pref ≈ 0:说明两派分数旗鼓相当,大模型此时极度纠结,不知道该信眼睛还是信常识。

总结

作者大费周章算这个 pref,就是为了把它当成标签(Label)

在下一步的岭回归中,我们要寻找一个高维方向(即 CPV 向量 wl),使得中间 MLP 层的隐藏状态 hl 在这个方向上的投影,正好等于这个 pref

MRV的提取

一、 MRV 的数据集设计:对称图文冲突(Setup II)

为了提取出纯粹的跨模态偏好,而又不引入特定词汇的频次偏见,论文设计了成对出现的对称冲突数据集,包含两个子集 SaSb

Sa = {(IA, qB, y+ = A, y = B)}

Sb = {(IB, qA, y+ = B, y = A)}

其中:

  • IX:包含物体 X 的图像。
  • qX:故意引导、暗示物体是 X 的文本提示词。

二、 用具体案例拆解这两个子集

我们假设物体 A猫(Cat),物体 B狗(Dog)

1. 样本子集 Sa(图是猫,文本说是狗)

  • 输入图像 (IA):一张的图片。
  • 输入提示词 (qB)"文本提示:图里有一只狗。请问图片里到底是什么动物?答:" (故意用文本误导模型)。
  • 阵营划分
    • T+(视觉阵营)= ["猫", "Cat"] (对应 y+ = A
    • T(文本阵营)= ["狗", "Dog"] (对应 y = B

2. 对称样本子集 Sb(图是狗,文本说是猫)

  • 输入图像 (IB):一张的图片。
  • 输入提示词 (qA)"文本提示:图里有一只猫。请问图片里到底是什么动物?答:" (同样用文本误导)。
  • 阵营划分
    • T+(视觉阵营)= ["狗", "Dog"] (对应 y+ = B
    • T(文本阵营)= ["猫", "Cat"] (对应 y = A

三、 为什么要大费周章做“对称设计”?

大模型对不同的 Token 本身就存在先验的“偏心”。比如,有的模型在预训练时见过无数次“猫”,它天然就更喜欢吐出“猫”这个 Token。

  • 如果我们只用 Sa(图猫文狗)去解回归,解出来的向量 w 可能会混入“模型对‘猫’这个字本身的偏好”,导致提取出来的方向不够纯净。
  • 采用了对称设计后,在 Sa 中,“猫”是 T+(视觉);而在 Sb 中,“猫”变成了 T(文本)。

这样一来,通过联合在这两个子集上运行提取流程,Token 自身的频率噪声会在数学上被相互抵消。 回归方程被迫只能去寻找那个与“视觉 vs 文本”博弈逻辑强相关的隐状态维度。

四、 MRV 的最终提取

当这批对称冲突的样本准备好之后,接下来的流水线动作就回归到了我们熟悉的节奏:

  1. 过模型算 pref

    每个样本进模型,在关键位置 t − 1 抓取 logits

    • Sa 里:pref = max ("猫") − max ("狗")
    • Sb 里:pref = max ("狗") − max ("猫")
  2. 存下隐状态 h:收集对应的中前层 MLP 隐藏状态。

  3. 岭回归拟合

    将所有样本的 (h, pref) 倒进岭回归公式中一步解出权重。

最终解出来的这个 wMRV,就是模态依赖向量

岭回归时就是把 SaSb 两个集合里所有的样本打包合并在一起(取并集 Sa ∪ Sb,作为一整个训练集扔进岭回归里去求解 w

在论文的数学表述里,这也正是用 Sa ∪ Sb 来表示的原因。

Signed Activation Steering (有符号激活导向)

什么是 Signed Activation Steering (有符号激活导向)?

完成了前期的线下提取(Offline Extraction)后,你手里已经握着两组核心的“指南针向量”:VFV(视觉保真向量)和 MRV(模态依赖向量)

Signed Activation Steering (SAS) 正是 CAS 框架在线上推理(Inference-time)阶段发起总攻的核心机制。它的任务非常纯粹:在模型生成文本的单次前向传播中,直接在特定层(L11-14)把这两个向量作为残差“拍”进隐藏状态里,强行修正模型的认知偏差。

核心干预公式拆解

在推理生成第 t 个 Token 时,对于目标干预层 l ∈ {11, 12, 13, 14},SAS 通过以下公式对 MLP 的输出进行动态拦截和修改:

$$\hat{h}_l^{(t)} = \underbrace{h_l^{(t)}}_{\text{原生的残差流输出}} + \underbrace{\gamma(t) \cdot (\alpha v_{\text{vfv}}^{(l)} + \beta v_{\text{mrv}}^{(l)})}_{\text{SAS 强行注入的残差}}$$

我们把这个公式里的每一个变量拆开来看,它们各司其职:

  • hl(t)原生隐状态。模型在当前生成步、当前层 MLP 原本吐出来的隐藏状态向量。
  • vvfv(l)vmrv(l)黄金方向轴。也就是前几步利用岭回归从该层提纯出来的 VFV 和 MRV 向量(即前面讨论的权重 wl)。
  • γ(t)步长调节因子。一个与生成时间步或 Token 相关的动态系数,用来在生成过程中微调干预的节奏,防止过度干预导致文本质量塌陷。
  • αβ全场的灵魂——有符号的强度系数(Signed Strength Coefficients)。它们不仅决定了干预的力度,更决定了干预的方向(正负号)

使用残差链接(注入)的巨大优势

论文之所以选择残差注入,而不是直接去替换(Replace)隐藏状态,原因有三:

  • 保证模型的“保真度”:隐藏状态包含 4096 个维度,里面大部分空间都在维持模型的语法、标点、上下文连贯性。如果我们用完全覆盖(Overwrite)的方式改写 h,大模型瞬间就会变成傻子,话都说不利索。而残差连接是向量加法,它只是在高维空间里把当前状态“推偏”了一点点,保留了绝大部分原生的生成能力。
  • 计算极其轻量(Zero Latency):不需要做复杂的矩阵乘法或注意力重计算,只是在 GPU 里做一次一维的 Element-wise Add,对推理速度完全没有任何负面影响。
  • 物理意义纯粹:因为我们在前一步提取 VFV 和 MRV 时,用的就是 MLP 层的输出隐藏状态。现在原路加回去,在数学上构成了完美的因果闭环。

为什么起名叫 “Signed”(有符号)?这背后有什么物理发现?

这背后承载了这篇论文最颠覆、最反直觉的核心发现。

在多模态大模型(MLLM)幻觉控制领域,以前的经典方法(如 VCD、MESA 等)都建立在一个朴素的假设上:模型之所以瞎编(幻觉),是因为它“视觉忽略”了,也就是看图看漏了。所以大家之前的做法全部是一味地“增强”视觉信号(类似于在残差流里永远用加号 +)。

但作者团队在对 LLaVA-1.5、Shikra、Qwen-VL、InstructBLIP 等多款主流模型进行系统性探针实验时,发现了明显的模型异质性(Heterogeneity)

模型表现分类 模型的内部认知特点 对 α 或 β 的符号要求
视觉过度疏忽型 天生对图像视而不见,极度依赖历史文本或语言先验。 需要正向引导(+:强行拉高视觉依赖。
视觉过度敏感型 容易过度解读视觉碎片中的非核心或不相关噪声,导致捕风捉影。 需要负向拉回(:适度压制视觉,反倒能缓解幻觉!

关键认知

每一个模型由于架构设计(如是否用了 Perceiver Resampler、Linear Projector)和指令微调数据的不同,其面对冲突时的偏好反应完全不同。有些模型需要“推一把”,有些模型需要“拉一把”。

这就是为什么系数 αβ 必须是带正负号的(Signed)。通过在线下小样本验证集上快速搜索出每个模型专属的有效符号(Effective Sign),SAS 实现了“因材施教”,这也是它能显著击败传统一刀切(Always positive)方法的核心密码。

Tempered Position Prior (TPP调和位置先验)

在上一节谈到 Signed Activation Steering (SAS) 的公式时,我们留了一个精妙的伏笔——步长调节因子 γ(t)

Tempered Position Prior (TPP,温和位置先验 / 调和位置先验) 正是决定这个 γ(t) 应该如何演变的底层核心机制。它优雅地解决了大模型激活导向(Activation Steering)中一个致命的隐痛:“干预过度导致的语言崩塌”

为了让你轻松彻底地理解 TPP,我们从大模型的致命弱点聊起,看看它是如何给干预机制戴上“紧箍咒”的。

一、 为什么需要 TPP?(原生的位置先验陷阱)

自回归大模型(LLM/MLLM)在生成文本时,对Token 的生成位置(Position)有着极度强烈的依赖。这就是原生的“位置先验”。

在做表征干预时,如果我们一刀切地在每一个位置 t 都加上相同强度的偏好向量(即 γ(t) = 1 恒定不变),会引发极其严重的灾难:

  1. 表征漂移(Representation Drift):MLP 层的残差流就像一条平稳流淌的河。如果你在每一个时间步都往里扔一块一模一样的石头(注入向量),整条河的生态会彻底失衡。隐状态会被强行推离模型原本的合理流形(Manifold)。
  2. 文本退化(Text Degeneration):在实验中表现为模型突然变成“复读机”(比如无限重复“黄色黄色黄色……”),或者语法彻底混乱,逻辑直接崩塌。

二、 TPP 的核心逻辑:什么是“Tempered(温和/调和)”?

TPP 的核心思想是:干预的强度,必须随着生成位置 t 与关键冲突位置 t 的距离,进行“温和的衰减(Tempering)”。

它给干预强度设置了一个动态的、基于位置先验的保护罩。最常见的 TPP 表现形式(即 γ(t) 的计算方式)通常遵循指数衰减或者阶梯窗口衰减

以最经典的指数衰减为例:

$$\gamma(t) = \begin{cases} 0, & t < t^\star - 1 \\ \exp(-\tau \cdot (t - (t^\star - 1))), & t \ge t^\star - 1 \end{cases}$$

这里面的设计极其讲究:

  • 关键点前(t < t − 1γ(t) = 0。模型在念前面的引言(比如“图片里的西瓜瓤是”)时,完全不进行任何干预,让模型保持 100% 的原生语言流畅度。
  • 爆发点(t = t − 1:干预强度达到巅峰(γ = 1)。在模型即将吐出关键颜色/物体字眼的前一刹那,全力一击,强行把模型的思维拉回视觉通路。
  • 调和衰减期(t > t − 1:随着模型不断往后生成 Token,t 越来越大,γ(t) 呈指数级迅速衰减趋近于 0(τ 是控制衰减快慢的超参数)。

流程图

image-20260601120555879

1. 左半部分:离线提取阶段(Offline Extraction)

这张图的左侧展示了如何从模型内部“提炼”出我们需要的控制工具。

  • 步骤 A:构造两类上下文冲突(Two Setups)
    • 图里会画出两条独立的数据流,分别对应两种不同的“挖坑”设定:
      • 设定 I(反事实图像):喂入类似“蓝色香蕉”的图,诱导模型在“视觉”与“参数化常识知识”之间打架,用于提炼 VFV(视觉保真向量,vvfv
      • 设定 II(对称图文冲突对):喂入图猫文狗、图狗文猫的成对数据,诱导模型在“视觉输入”与“提示词误导文本”之间打架,用于提炼 MRV(模态依赖向量,vmrv
  • 步骤 B:在临界点实施数据抓取(Data Probing)
    • 图上会标识出一个关键点:当模型即将输出核心词(位置 t)时,算法会回退一步(在位置 t − 1)去拦截两样东西:
      1. 最后一层输出全词表的分数差值:即由两派最强 Token 分数作差得到的行为标签 pref
      2. 目标干预层 l 的 MLP 输出的隐藏状态向量 hl
  • 步骤 C:逐层岭回归(Layer-wise Ridge Regression)
    • 图的中间会画一个回归拟合的图标,代表把收集到的海量对齐数据 (hl, pref) 倒进 2 正则化的线性方程里去求解。
    • 最终产物:解出来的权重矩阵,就是图里画的两个专属一维黄金方向向量:vvfv(l)vmrv(l)

2. 右半部分:在线推理中的激活导向(Online Inference Strategy)

这张图的右侧展示了当模型在实际应用(如回答用户的开放式提问)时,我们如何动态拦截并修复幻觉。

  • 步骤 A:单阶段正常前向传播(Single-pass Forward)
    • 与对比解码(VCD 等方法)需要跑两遍前向传播(一遍正常,一遍变焦/模糊图)不同,右侧图展示的是标准的、唯一的自回归生成路径。用户输入一张新图和 Prompt,模型正常向前计算。
  • 步骤 B:中前层 MLP 拦截与插桩(MLP Hooking)
    • 右图会特意放大模型的 L11–L14 这一层带。当残差流传导到这四个特定的中前层 MLP 输出端时,SAS(有符号激活导向)机制像微创手术一样横插一脚,对原生的隐状态 hl(t) 施加一个加性残差(Additive Residual)
  • 步骤 C:动态残差公式的在线组装
    • 图上会标出注入的完整项:γ(t) ⋅ (αvvfv(l) + βvmrv(l))
    • α, β(有符号强度调节):根据具体模型的“性格”(是视而不见,还是捕风捉影),赋予正号或负号,决定是把模型往视觉方向推,还是从视觉噪声里拉回来。
    • γ(t)(TPP 位置门控):它像一个动态阀门。在图上通常会用一个由高到低的衰减曲线来表达。在即将吐出关键词的那一瞬间,阀门全开(γ = 1);随着文本往后生成,阀门快速关闭趋近于0(γ → 0),防止污染后面正常的语法。

实验设置 (Experimental Setup)

1. 评估模型 (Models) —— 覆盖四大主流架构

为了证明 CAS 框架具有跨架构的普适性,实验并没有局限于单一模型,而是挑选了四个最具代表性的开源多模态大模型(MLLM) :

LLaVA-1.5

Shikra

Qwen-VL

InstructBLIP

学术亮点:这四个模型在跨模态融合设计(如线性投影 Linear Projector、交叉注意力 Cross-Attention、Perceiver 等)上存在显著差异 。CAS 能在这些迥异的架构上同时生效,有力证明了其在模型隐藏空间实施“表征微创手术”的通用性 。

2. 评估基准 (Benchmarks) —— 自由生成与是非判别双轨制

评测任务全面覆盖了生成式(Generative)判别式(Discriminative)两大家族,不给任何作弊手段留死角 :

  • 生成式任务 (Generative Task)
    1. CHAIR:在 500 张 COCO 图像上运行详细描述任务,报告 CHAIRS(句级幻觉率)、CHAIRI(词级幻觉率)和 F1 分数 。
    2. AMBER (生成式划分):包含 1,004 张图像,报告 Hal(整体幻觉率)、CHAIR、Cog 和 Cover(物体覆盖率) 。
  • 判别式任务 (Discriminative Task)
    1. POPE:包含 Random(随机)、Popular(高频)、Adversarial(对抗)三个子集,每个子集包含 3,000 个问答对 。最终报告 Acc(准确率)和 F1 分数 。

3. 对比基线 (Baselines) —— 八大强劲对手大围剿

实验将原生模型(Vanilla)与近年来发表在顶会上的 8 种主流的、无需训练(Training-free)的抗幻觉方法进行了全面横向对比,按发表时间排序 :

DoLa (2024)

VCD (2024)

OPERA (2024)

PAI (2024)

Code (2024)

DeCo (2025)

AttnReal (2025)

SSL (2025)

4. 引入新文本质量指标 —— 堵住“复读机”漏洞

作者在实验中发现了一个行业痛点:部分基线方法(如 DoLa、OPERA、PAI 等)虽然降低了幻觉,但代价是破坏了语言模型的能力,导致模型在推理时陷入严重的重复生成(复读机退化)

  • 更糟糕的是,传统的 CHAIR 评估由于不进行去重计数,重复喷出正确的物体反而会稀释幻觉率(人为刷低幻觉指标)
  • 为此,作者额外引入了 Rep(n-gram 重复率) 指标 。在最终的结果表里,凡是导致模型重复退化的数据,都会被红色标出 。

5. CAS 的专属硬编码配置 (CAS Configuration)

在线上干预时,CAS 的策略保持了高度的简洁和克制 :

  • 干预位置:固定为模型的中前层,即 L11–L14 层的 MLP 输出
  • 干预方式:执行单次有符号残差注入(即利用前面通过岭回归解出的 αβ 有符号系数进行向量加法) 。

6. 解码策略与硬件环境 (Decoding & Hardware)

为了确保学术评测的可复现性和严谨性,实验排除了随机采样(Sampling)带来的方差影响 :

  • 解码策略:所有方法一律使用 贪心解码 (Greedy Decoding)
  • 长度限制:CHAIR / AMBER 任务设置 max_new_tokens = 512(长文本生成);POPE 任务设置 max_new_tokens = 64(短文本是非问答) 。
  • 算力消耗:所有实验均在单张 NVIDIA RTX 3090 上完成,充分体现了无需训练方法的低算力成本优势 。

贪心解码(Greedy Decoding)

一、 核心逻辑与工作原理

在大模型生成文本时,它是逐个 Token(字/词碎片)自回归生成的。在每一个时间步,模型会为词表里的所有 Token 算出对应的概率分布(即我们之前提到的 Logits) 。

贪心解码的规则非常简单、粗暴:它在每一步只挑选概率最高(或者得分最高)的那一个 Token,绝对不考虑其他任何人选。

具体的数学表达:

Next Token = arg max (Logits)

举个例子(模型正在回答“草莓是什么颜色”):

在预测下一个字时,词表张榜:

  • "红":概率 60%
  • "绿":概率 15%
  • "粉":概率 10%
  • ……

如果是贪心解码,模型会毫不犹豫地直接选择 "红"。因为它的概率最大。选完 "红" 之后,再把 "红" 拼进输入里,去预测再下一个字,以此类推。

二、 贪心解码在论文中的关键作用

在你上传的《TSAI:面向多模态大语言模型幻觉缓解的时序-语义注意力干预》论文中,作者在 4.1 实验设置(Experimental Setup) 里特别强调了他们统一采用贪心解码作为所有模型和方法的基准,这背后有非常严谨的学术考量:

隔离随机干扰(确保公平):普通的文本生成往往会加入随机采样(如 Nucleus Sampling / Top-p),这会导致模型每次运行吐出的词都不一样。论文指出,采用贪心解码可以“隔离随机采样算法引入的性能差异,确保严格公平的比较” 。

提供最稳定的基线(Baseline):论文强调,对于评估抗幻觉能力的确定性评测(如是非题 POPE 或描述题 CHAIR),“贪心解码通常能产生最稳定且具有竞争力的基线性能” 。所以表 1、表 3、表 4 中,最基础的对照组(Vanilla)数据就是原生模型在贪心解码下的表现 。

三、 贪心解码的优缺点横向对比

为了让你的概念体系更完整,我们可以把它与束搜索(Beam Search)、核采样(Nucleus / Top-p Sampling)进行横向对比:

解码策略 核心机制 优点 缺点 / 在抗幻觉中的表现
贪心解码 (Greedy) 每一步无脑选 max  速度极快、100% 可复现、结果极其稳定。 缺乏创造力。在长文本生成中,极其容易陷入“语言退化”(表现为变成复读机,不断循环一句话) 。
束搜索 (Beam Search) 每一步保留 Top-k 个最优路径,全局综合评分。 逻辑严谨、生成文本质量高。 容易产生“过度信任(Over-Trust)”,即模型太相信前面自己编过的话,从而容易导致幻觉滚雪球 。
核采样 (Top-p / Nucleus) 在累计概率前 p% 的候选词里随机抽。 文本丰富、拟人度高。 带有随机性,不方便做严谨的抗幻觉算法效能消融实验。

Generative Hallucination(生成式幻觉评估)

Table 1:CHAIR 评测结果(物体幻觉的硬核较量)

Table 1 通常是整篇论文的“主战场”,它展示了 Vanilla(原生模型)、CAS 框架以及 8 种顶会 Baseline 在 LLaVA-1.5、Shikra、Qwen-VL 和 InstructBLIP 这四大模型上的全面横向对比。

image-20260601130346429

1. 核心指标解读

在这个表里,你会看到以下几个核心列:

  • CHAIRS(句级幻觉率)CHAIRI(词级幻觉率):这两个指标越低越好。CAS 的核心卖点就是在这两项上拿到了显著的最低值(Bold 粗体标注)。
  • Rep(n-gram 重复率):这是作者为了打假而引入的创新指标,越低越好。它统计了模型生成文本中出现“复读机”现象的严重程度。

2. 揭露 Baseline 的“作弊”现象(学术亮点)

在 Table 1 中,你会注意到许多传统方法(如 DoLa、OPERA、PAI 等)在某些模型上的 CHAIR 指标看似很低,但它们的 Rep(重复率)数值却高得离谱,在表中被作者用红色(Red)刺眼地标出

  • 背后的黑幕:因为 CHAIR 的计算公式是 $\frac{\text{幻觉词数}}{\text{总词数}}$。一些对比方法因为破坏了语言模型的能力,导致模型在推理时不断重复一句话(例如:“There is a cup, a cup, a cup…”)。由于“cup”是图里真实存在的正确物体,这种无限复读正确词汇的行为人为地扩大了分母,从而恶意稀释了幻觉率
  • CAS 的降维打击:CAS 在大幅降低 CHAIRSCHAIRI 的同时,保持了极低的 Rep(与 Vanilla 原生模型几乎一致)。这有力地证明了 TPP(温和位置先验) 机制的成功——干预向量在关键 Token 处精准纠偏后迅速退场,绝不污染后面的正常文本生成。

Table 2:AMBER-G(生成式幻觉)评测结果分析

在这一部分中,评测完全聚焦于长文本自由描述场景。大模型在没有是非题“喂饭”的情况下,自己主动看图说话。Table 2 展现的核心价值在于:CAS 在“盲考”状态下对复杂生成任务的统治力。

image-20260601130400358

1. 核心指标矩阵

在 Table 2 中,你需要重点记录这三个核心指标的黄金三角关系:

  • Hal(整体句级幻觉率):评估生成文本中,包含存在、属性、关系等各类综合幻觉的句子比例。数值越低,说明整体越诚实。
  • CHAIRS/CHAIRI(物体级幻觉):精准定位到名词层面的捏造比例。
  • Cover(物体覆盖率 / 召回率)全场含金量最高的指标。它统计了大模型到底把图片里真正存在的物体认出来了多少。

2. 为什么 AMBER-G 的数据能对 Baseline 形成降维打击?

在自由生成任务中,最容易出现的两个学术大坑是“因噎废食”“语言塌陷”。Table 2 的数据直接自证了 CAS 是如何完美避开这两个坑的:

  • 打破“说得越少,错得越少”的作弊死循环

    很多对比方法(比如传统对比解码)为了追求极低的幻觉率(HalCHAIR),干预力度过大,直接导致模型变得极度保守、不敢说话。反映在数据上,就是它们的 Cover(物体覆盖率)会出现断崖式下跌。

    • CAS 的优势:CAS 在把 HalCHAIR 砸到最低的同时,Cover 指标依然能够保持高位,甚至超越 Vanilla(原生模型)。这证明“有符号激活导向”不是简单地给模型贴封条,而是真正提升了模型对底层视觉特征的有效提取和正面感知。
  • 证明 TPP(温和位置先验)的不可或缺性

    自由生成长文本对隐空间的扰动极其敏感。CAS 能够完美跑通 AMBER-G 的长文本 Captioning 任务,并在综合得分上拿到 SOTA,在图表层面上直接宣告了 TPP 衰减机制的胜利——干预向量在 t − 1 位置精准爆发,修正了关键物体的倾向后优雅退场,将后半句的语法和叙事主导权还给语言模型,从而保证了长文本的顺畅。

判别式幻觉(Discriminative Hallucination)

image-20260601132110896

Table 3 的纵纵横横:结构与核心指标

在论文的 Table 3 中,数据矩阵通常按照以下维度展开:

  • 纵轴(Rows):四大主流多模态模型(LLaVA-1.5、Shikra 等)作为大组,每个大组内横向对比 Vanilla(原生模型)以及各大主流 Baseline,最后以 CAS(Our) 收尾。
  • 横轴(Columns):划分出 POPE 的三个经典子集:Random(随机)Popular(高频)Adversarial(对抗)。每个子集下报告两个核心指标:
    1. Accuracy (Acc,准确率 ):整体答对的比例。
    2. F1-score ():平衡了精确率和召回率的综合指标(全场最重要的评判依据)。

Per-model response to VFV and MRV

这一部分聚焦于 CAS 框架中最核心的实证发现:不同模型对“视觉保真向量(VFV)”和“模态依赖向量(MRV)”的异质性反应(Per-model response)

核心发现:模型异质性(Model Heterogeneity)与符号搜索

在表征工程(Representation Engineering)中,最理想的状态是“一个黄金向量包治百病”。但 CAS 论文在这里揭示了一个残酷的现实:即使提取流程完全一样,同一个偏好向量对不同模型而言,其最佳干预方向(正号 + 还是负号 )和干预力度(模长大小)是完全相反且割裂的。

作者将这种现象称为模型异质性。它直接决定了为什么线上注入残差时,强度系数 αβ 必须是有符号的(Signed)

Figure 3 深度解码:符号与强度的全面吞吐流(Grid Search 趋势)

Figure 3 在论文中通常是一组折线图(Line Charts)或热力图(Heatmaps)。它展示了当干预系数 α(控制 VFV)和 β(控制 MRV)在区间(例如 [−3.0, +3.0])内连续滑动时,各大模型在抗幻觉指标(如 POPE 准确率、CHAIR 幻觉率)上的动态性能表现曲线

image-20260601134337395

1. 性能曲线的“单峰特性”与零点跨越

  • 如果你观察图 3 的曲线,会发现绝大多数模型的性能并不会随着干预强度的增大而无限变好,而是呈现出完美的单峰(Single-peak)结构
  • 关键在于,这个性能巅峰(Peak)落在零点(Vanilla 原生模型)的左边还是右边
    • 如果巅峰在右边(正值区间),代表该模型属于“视觉疏忽型”,越补正向视觉信号越聪明。
    • 如果巅峰在左边(负值区间),代表该模型属于“捕风捉影型”,加了负号(压制视觉噪声)反而能刷新 Acc 分数。

2. 悬崖效应(Cliff Effect):过度干预的语言崩塌

  • 无论模型的最佳符号是正还是负,一旦干预强度(绝对值)越过了那个最佳的“甜点区(Sweet Spot)”,图 3 中的性能曲线会展现出恐怖的断崖式下跌
  • 学术本质:这是因为过大的残差向量注入直接干扰了语言模型(LLM)底层基座的指令遵循与语法概率流,导致隐状态彻底偏离了合理的表征流形(Representation Manifold),模型开始在生成端胡言乱语。

Table 6 & Table 7 深度解码:超参数图谱与稳健性验证

如果说 Figure 3 是连续的宏观大势,那么被放在附录或实验深入分析部分的 Table 6 和 Table 7 则是 CAS 团队留下的硬核工程账本。它们通常分别记录了最优符号与强度的全局搜索结果,以及跨任务/跨层干预的消融边界

image-20260601142307660
image-20260601142336909

Layer Analysis层消融实验

image-20260601142809876

为了验证干预层的选择,我们将相同的CPV置于LLaVA-CHAIR的不同四层窗口中。为了放大窗口间的差异,我们使用了更强的固定强度(MRV β=+2,VFV α=−2)。如表4所示,两种CPV在不同窗口中表现出一致的模式:在我们扫描的窗口中,中早期层L11–14是唯一有效且稳定的层;从L16开始,效果恢复到原始水平,基本为零;在浅层L1–4和L6–9,MRV和VFV都会引发严重的退化并导致失控的重复。因此,我们在所有实验中将干预固定在L11–14层。

Conclusion和Limitations

一、 结论部分 (Conclusion)

结论部分对 CAS 框架的成效给出了最终定性,核心成果可以概括为以下三点:

  • 开创了表征层面的“因材施教”范式:论文成功打破了传统“一刀切增强视觉”的思维定势,首次证明了 MLLM 内部隐空间存在模型异质性。通过首创的有符号激活导向(SAS),实现了对不同缺陷型模型(视觉疏忽型 vs 视觉敏感型)的精准对症下药。
  • 抗幻觉与流畅度的帕累托最优(Pareto-optimal):在三大硬核基准(CHAIR、POPE、AMBER-G)上,CAS 在大幅刷新最低幻觉率的同时,完美保护了模型的生成流畅度(Rep)与细节丰富度(Cover)。这宣告了 Tempered Position Prior (TPP) 位置门控机制的全面胜利。
  • 兼顾极高部署性价比:作为一种无需训练(Training-free)的方法,CAS 仅在单次前向传播中进行极其轻量级的加性残差操作,不引入任何额外的解码延迟(Zero extra decoding latency),彻底解决了对比解码(VCD、CODE 等)推理速度翻倍的工程痛点。

二、 局限性部分 (Limitations)

作者非常坦诚地分析了 CAS 框架在现阶段的局限性与改进空间,这也是未来开展研究生工作或后续大创项目的绝佳发力点:

1. 严重依赖离线网格搜索(Hyperparameter Dependency)

虽然 CAS 推理时是零开销,但在正式上线前,它必须依赖一个小规模的冲突验证集,通过网格搜索(Grid Search)去试出该模型专属的最佳符号和强度系数(αβ)。

  • 痛点:这种“调参”过程是离线的(Offline)。如果换了一个全新的、完全没见过的微调模型,用户无法盲打注入残差,必须先跑一次快速的离线校准。

2. 幻觉防御的范畴边界(Scope Limitations)

CAS 提取的黄金方向向量(VFV 和 MRV),其核心对齐逻辑建立在“物体存在性”“模态冲突”上。

  • 痛点:多模态幻觉的成因极度复杂。如果模型发生的不是物体层面的幻觉,而是高级的多步跨模态推理错误(Reasoning Error)、复杂的空间几何倒错,或者是在长视频理解(Long-form Video)中产生的时序颠倒,现有的 VFV 和 MRV 向量可能无法提供足够强大的因果纠偏能力。

3. 静态语义轴与动态生成上下文的脱节(Static CPV Vector)

岭回归解出来的 CPV(上下文偏好向量)在训练集拟合完成后,其高维方向就完全固定了。

  • 痛点:在实际生成长文本时,虽然有 TPP 门控在时序上让它衰减,但这个向量本身并不会根据模型当前吐出的具体词汇语境发生动态的形状自适应变化。它主管的是大方向上的“认知站队”(信眼睛还是信常识),缺乏例级自适应(Instance-specific Adaptive Steering)的细粒度微调。

4. 超大尺度模型(Ultra-Large Models)的层定位成本

实验虽然在 7B/8B 尺度上的四大主流架构上表现得极为稳健(最优干预层均收敛在 L11–L14),但如果未来基座模型扩展到 14B、32B 甚至 70B 时,知识存储的中枢层带必然会发生漂移。

  • 痛点:这意味着在面对超大模型时,研究人员必须重新做一遍 4.5 章节那套高成本的“层消融实验(Layer Analysis)”,才能重新定位到新模型的“黄金干预窗口”。

附录

超参数设置

image-20260601144400758

推理延迟

image-20260601144445230

我们在 LLaVA-1.5 上测量了每种方法的每 token 解码延迟,结果如表 5 所示。CAS 在单次前向传播中仅对四个中早期层的 MLP 输出进行加性残差操作,没有额外的前向传播,也没有对比解码。因此,其每 token 延迟接近于 vanilla 方法,且远低于双前向方法(PAI / VCD / CODE)和多步搜索方法(OPERA)。

问题

怎么想到提取这两个向量的

为什么作用在mlp层,是类比lora吗

TPP真的巧妙

训练干预模型?

SSE(Server-send events)

SSE 是一种单向通信技术,允许服务器主动向客户端(通常是浏览器)推送实时数据。

在过去,网页想要获取服务器的实时数据,要么用短轮询(每隔几秒发一次 HTTP 请求,低效且浪费资源),要么用 WebSocket(双向奔赴,但协议较重,开发成本高)。而 SSE 则是介于两者之间的一个轻量级极佳方案。

核心工作原理

image-20260527185951958

1. 建立长连接(Keep-Alive)

普通的 HTTP 请求是“一问一答”,服务器说完话连接就立刻断开。而 SSE 在客户端发起请求后,服务器会返回特殊的响应头,告诉浏览器:“这个连接别挂断,后面还有货!”

这组特殊的响应头通常是:

  • Content-Type: text/event-stream:告诉浏览器,这是一个流式的数据流。
  • Cache-Control: no-cache:禁止缓存,确保数据的实时性。
  • Connection: keep-alive:保持长连接。

2. 文本流数据格式

连接建立后,服务器就可以通过这个通道持续发送文本数据。SSE 对发送的数据格式有严格的要求,它是由多条消息组成的文本流,每条消息内部用换行符 \n 分隔,消息之间用两个换行符 \n\n 分隔。

最基本的结构如下:

1
data: 这是第一条消息\n\n

id: 1\n event: chat\n data: 这是第二条带事件类型和ID的消息\n\n

3. 自带“断线重连”基因

在网络不稳定的现实世界中,连接断开是常有的事。SSE 的高级之处在于,浏览器的原生 API(EventSource)内置了自动重连机制

  • 如果网络断开,浏览器会自动尝试重新连接服务器(默认每隔 3 秒)。
  • 服务器在发送数据时可以附带一个 id 字段。断线重连时,浏览器会自动在 HTTP 请求头中带上 Last-Event-ID: [上次收到的ID]。服务器看到后就能心领神会,把客户端错过的消息补发给它。

Claude 的 Messages API 与 OpenAI 的 Responses API

前者是无状态(Stateless),每次对话会将历史记录传回服务端,利用promptcaching技术(大模型服务器의网关会计算你这段历史文本的哈希值(Hash),当发现前面的字符和之前一致时,会从内存的 KV Cache读取,而不是重新计算)。

前者第二个特点是结构上的,核心差别就是claude选择使用数组存储,称之为 (Content Blocks),里面按时间顺序并列存放不同的“块”(如思维链块、文本块、工具调用块)。这里可以和传统的openaiCompletions api进行对比思考,传统的Completions是将对话存入一个”message”对象的字段下,“message”对象内包含”content”“reasoning_content”“role”等字段。

但核心问题就是,JSON 语法绝对不允许出现重复的键(Key),如果单次对话出现多次工具调用或思考,Completions很难正确表示这个过程。

我的分析是,旧的Completions还停留在与模型对话一问一答的形式,而现在agent通常一次对话包含多次工具调用或模型思考,因此Completions不再适用,从而claude选择使用数组存储多个json对象,每个json使用type区分块的功能

后者结构上沿用和Claude相同的设计,使用json数组实现对对话内容的存储,每次请求和响应只会包含最新的一轮对话,而不像claude完整的历史记录,每个Response都会包含一个id,从而支持随时回滚;而item的id可以类比claude的type,说明单次的功能。

Plaintext

1
2
3
4
5
6
7
[第 1 轮对话]
Response (最顶层对象) ──>【你的本地数据库存它!】id: "**resp_A1B2C3...**"

└── output (内部数组)
├── Item 1 (思考块) ──> id: "**item_thinking_001**"
├── Item 2 (工具调用) ──> id: "**item_tool_002**"
└── Item 3 (最终回复) ──> id: "**item_text_003**"

后者最大的特点是在服务端是有状态(Stateful)工具调用和上下文历史管理完全托管在云端服务器

这样的好处有:

  1. 发新请求时,不用反复传完整的聊天记录,只要带上上一次回复的 previous_response_id,服务端会自动在后台回溯、拼接历史,甚至自动触发上下文压缩(Compaction)
  2. 云厂商将网络搜索、代码解释器等工具直接托管在云端。在单次 API 请求的生命周期内,模型如果发现需要联网或跑代码,会在云端沙盒里自己反复调用并纠错,跑完整个智能体循环后,直接把最终成果包吐给客户端,客户端只负责“解析最终数据”。
  3. 模型方面,openai可以因此保护模型的隐藏推理链(Reasoning Traces),OpenAI 就可以把所有的推理中间态完美地封锁在自己的云端服务器里,客户端只能拿到剥离后的干净结果。
  4. 数据方面,一旦完全托管在服务端,由于数据是由 OpenAI 的数据库统一规范存储的,服务器在计算 KV Cache 的持久化和重用时,可以做到对齐和命中

但这样也有坏处:

由于是云端调用工具,除了 Token 费,调用内置的联网搜索、代码沙箱通常需要额外支付固定通道费或容器会话费

image-20260527180345112

prompt caching

kvcache的原理不多赘述,简单来说,就是大模型会把kv矩阵计算的值存储起来,再次遇到历史字符时就不用计算了,只计算新字符的矩阵,最后拼接起来。

对于Completions API和Anthropic 官方的 Messages API,他们每次请求与详细都会传输完整的上下文,因此为了节省token的消耗,prompt caching便是关键

这里可以参考提示缓存 |OpenAI API

模型提示通常包含重复内容,如系统提示和通用指令。OpenAI 将 API 请求路由到最近处理过相同提示的服务器,这使得它比从零开始处理提示更便宜、更快。提示缓存可将延迟降低高达80%,输入令牌成本降低高达90%。提示缓存会自动处理你所有的 API 请求(无需修改代码),且没有额外费用。

缓存命中只能针对提示内的精确前缀匹配。

image-20260527185035144

工作流程

缓存功能对于1024个token及以上的提示会自动启用。当您发送API请求时,会执行以下步骤:

1.根据提示词初始前缀计算哈希值,去内存的 KV Cache 索引表里检索。

2.如果找到匹配的前缀,系统会使用缓存结果。

kv的存储

它最初在 GPU 显存(VRAM)里,但为了存更久,会被“吐”到 GPU 本地的 NVMe SSD 硬盘或内存中。

大模型的显存(VRAM)是极其昂贵且稀缺的资源。如果把所有用户的 KV 缓存都永久塞在 GPU 显存里,GPU 瞬间就会爆显存(OOM)。因此,厂商引入了分层存储架构:

  • 热缓存(In-Memory 状态):当模型刚处理完你的请求时,生成的 KV 张量确实老老实实呆在 GPU 显存(VRAM) 或该服务器节点的 系统内存(RAM) 中。因为读写速度最快,随时准备应对你几分钟内的下一轮对话。

  • 冷缓存(Extended 状态):当缓存需要保存几小时甚至 24 小时时,服务器会把这些 KV 张量进行加密,然后从珍贵的显存中释放出来,转储到 GPU 本地服务器的高速 NVMe SSD 硬盘 上。

    当你下一次请求命中缓存时,服务器会以极快的速度把加密的 KV 张量从本地硬盘重新加载(Load)回 GPU 显存中。虽然比纯显存慢一点点,但比起让大模型重新推理一遍几万字的输入,速度依然快了 80% 以上。

openai Completions API和Responses API区别

1. 响应数据结构(JSON)对比

两者的最直观区别在于返回的 JSON 报文层级和字段设计。

传统的 Completions API (以 Chat Completions 为例)

在传统规范中,模型生成的内容被深度嵌套在 choices 数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1716584520,
"model": "qwen-plus",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!请问有什么我可以帮你的?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}
  • 代码提取路径response.choices[0].message.content
  • 痛点:层级过深;由于早期的设计允许返回多个结果(通过 n 参数控制 choices 的数量),导致普通的单次对话也必须套一层数组。

全新的 Responses API

为了简化代码并原生支持更复杂的 AI 行为,新规范将结构打平,引入了 output 概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"id": "resp_123",
"object": "response",
"model": "qwen3.5-122b",
"output": [
{
"type": "message",
"reasoning": "用户询问两者的区别,我需要用表格和对比清晰展现...",
"message": {
"role": "assistant",
"content": "两者的主要区别在于..."
}
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 45,
"total_tokens": 60
}
}
  • 代码提取路径:在新版 SDK 中,通常可以直接通过 response.output_text 拿到最终文本。
  • 亮点原生支持深度思考(Reasoning)。当使用具备类 o1/Qwen3.5 等带有思维链(CoT)功能的推理模型时,其思考过程会直接作为平级的 reasoning 字段输出,无需再像以前那样通过特殊的 content 拼接或不透明的魔改字段来传输。

2. 核心维度深度对比

维度 Completions API (传统) Responses API (新一代)
设计定位 纯文本生成 / 简单对话交互 彻底面向智能体 (Agentic Loop) 和新型推理模型设计
结构复杂度 较高。嵌套在 choices[0].message 较低。外层通常直接暴露 output 或扁平化字段
思维链支持 (Reasoning) 较弱。通常需要占用 content 空间或使用魔改字段透传 原生支持。拥有独立的 reasoning 字段,与最终回答完美分离
多步骤工具调用 (Tool Call) 复杂。多轮 Tool 调用需要开发者在代码里频繁手动拼装、维护上下文历史 原生自动化。支持单次请求内模型自主进行多步工具调用(如网络搜索、执行代码)并直接返回最终 Response
存储与状态保持 默认无状态(Stateless),每次交互需全量上传历史记录 部分环境支持原生上下文状态保持(Stateful),降低长对话的 Token 传输成本

Anthropic 官方的 Messages API 规范

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"id": "msg_01A4zFr95E9A3daS7kL28xWq",
"type": "message",
"role": "assistant",
"model": "claude-3-7-sonnet-20250219",
"content": [
{
"type": "text",
"text": "没问题,我正在为您查询数据库中状态为活跃的前5个用户。"
},
{
"type": "tool_use",
"id": "toolu_01827391aBcDeFgHiJk",
"name": "query_database",
"input": {
"sql": "SELECT user_id, username FROM users WHERE status = 'active' LIMIT 5;"
}
}
],
"stop_reason": "tool_use",
"stop_sequence": null,
"usage": {
"input_tokens": 420,
"cache_creation_input_tokens": 350,
"cache_read_input_tokens": 0,
"output_tokens": 85
}
}

核心对比矩阵

维度 Claude 响应格式 (Messages API) OpenAI Responses API
设计核心 富内容块 (Rich Content Blocks) 驱动 智能体循环 (Agentic Loop) 与托管工具驱动
基础数据对象 消息 (messages) 与 内容块 (content) 项 (items) 与 响应对象 (response)
多轮状态管理 无状态 (Stateless):需由客户端每次回传完整上下文(配合 Prompt Caching 优化成本) 有状态 (Stateful):支持通过 previous_response_id 在服务端自动链式追踪状态
工具调用机制 客户端编排:模型返回 tool_use,开发者在本地执行后用 tool_result 回传 服务端托管:一次请求内,服务端自动运行多轮内置工具(Web 搜索、代码执行、MCP等)
结构化输出 支持 JSON Schema / 借助 SDK 的 messages.parse 确保强一致性 原生支持强类型的 Structured Outputs 和自动修复

核心差异深度解析

1. 返回数据结构:Content Blocks vs Output Items

  • Claude 的响应格式(块结构)

    Claude 的核心创新在于将响应内容拆解为一个个并列的 Content Block(内容块)。一条响应的 content 是一个数组,里面可以同时包含思维链(thinking)、文本、工具调用等多种类型。

    JSON

    1
    2
    3
    4
    5
    "content": [
    { "type": "thinking", "thinking": "用户需要计算..." },
    { "type": "text", "text": "好的,我为你算一下。" },
    { "type": "tool_use", "id": "toolu_123", "name": "calculator", "input": {...} }
    ]

    这种设计的优势在于透明度高、扩展性极强,完美融合了文本、多媒体(PDF/图片)和原生推理。

  • OpenAI Responses API(对象与项结构)

    Responses API 彻底抛弃了早期 Chat Completions 繁琐的 choices[0].message 嵌套,返回一个具有独立 idresponse 对象,其内部核心是 output 数组,由各种类型的 Item(项) 组成。

    1
    2
    3
    "output": [
    { "id": "item_abc", "type": "message", "message": { "role": "assistant", "content": [...] } }
    ]

    它在顶层解耦了 instructions(系统指令)和 input(用户输入),语义更加符合现代 Agent 架构。

2. 状态与多轮对话管理

  • Claude:客户端保留 + 提示词缓存 (Prompt Caching)

    Claude API 本身是无状态的。这意味着做多轮对话时,你必须把之前的聊天历史全部打包传回去。

    巧妙的优化: 尽管每次都要重传历史,但 Claude 提供了极其强大的 Prompt Caching(瞬时缓存)。只要历史对话没有变,重复传入的部分会直接命中缓存,不仅响应速度极快,还能帮你省下高达 90% 的输入 Token 费用。

  • OpenAI Responses API:服务端自动追踪 + 引用 ID

    Responses API 引入了真正的“有状态”模式。你不需要在客户端维护一个长长的数组,你只需要传入当前最新的用户输入,并附带上一次的 previous_response_id

    Python

    1
    2
    3
    4
    5
    response2 = client.responses.create(
    model="gpt-5",
    previous_response_id=response1.id,
    input="那它的常住人口是多少?"
    )

    OpenAI 的服务器会自动在后台保留和拼接历史,甚至在上下文过长时自动触发服务端压缩 (Compaction),免去了开发者手动管理长上下文的痛苦。

3. 工具调用与智能体编排 (Tool Use & Agentic Loop)

  • Claude:协作式编排

    当你给 Claude 绑定自定义工具时,交互是“回合制”的:客户端发请求 Claude 返回 tool_use 块并中断请求 你的代码在本地执行该工具 你的代码把 tool_result 发回给 Claude Claude 给出最终文本。这种模式下,控制权完全在开发者手里,你可以非常安全地在本地读取数据库或执行敏感操作。

  • OpenAI Responses API:全面托管的自动循环

    Responses API 的最大杀手锏是它的 Agentic by default(默认智能体化)。它把工具调用的控制权收归到了云端。

    它内建了诸如 Web Search(网络搜索)、File Search(文件搜索)、Code Interpreter(代码解释器)以及远程 MCP(模型上下文协议)服务器。当你发出一个请求时,模型可以在一次 API 调用的生命周期内,自己在云端反复调用多次工具,直到得出完美答案。开发者不需要写任何多轮编排的代码。

参考资料

Data controls in the OpenAI platform

Prompt caching | OpenAI API

简介

今天尝试使用hugging的transformer库跑通litert-community/DeepSeek-R1-Distill-Qwen-1.5B · Hugging Face

Qwen-1.5B 基底:它使用了阿里云开源的 Qwen2.5-1.5B 作为基础架构,参数量只有 15 亿(1.5 Billion),天生体积小、运行速度快。

DeepSeek-R1 蒸馏:DeepSeek 把他们那个 6710 亿参数、拥有强大推理思维能力的巨型模型(DeepSeek-R1)所产生的思维链(Chain of Thought)数据和推理样本,喂给了这个 1.5B 的小模型。

核心能力:这意味着它虽然只有 15 亿参数,却继承了 DeepSeek-R1 的“思考”习惯(会在 <think> 标签内进行自我纠错、逻辑推理),在数学、代码和逻辑推理上,性能远超普通的 1.5B 级别模型。

tranformer库和pytorch的区别

PyTorch(底层深度学习框架)

  • 角色:通用数学和张量(Tensor)计算库,专门为深度学习设计。
  • 干什么:它提供最基础的算子。比如图下半部分看到的 SoftmaxLinear(全连接层)、矩阵乘法、梯度反向传播,以及直接对接显卡硬件(NVIDIA CUDA、AMD ROCm、Apple Metal)的驱动支持。
  • 特点:它本身不知道什么是大语言模型,什么是 Llama 或 DeepSeek。它只知道如何高效地在 GPU 上做矩阵加减乘除。

Transformers 库(上层模型生态库)

  • 角色:由 Hugging Face 开发的、专为 Transformer 架构模型定制的高级封装库
  • 干什么:它把 PyTorch 提供的基础算子(Linear、Softmax 等)拼接起来,组装成一个个具体的经典模型结构(如 DeepSeek、Qwen、Llama)。
  • 特点:它让你不需要从零去写注意力机制(Attention)的数学公式,直接通过几行代码(如 AutoModelForCausalLM.from_pretrained('deepseek-ai/DeepSeek-R1'))就能把整个模型跑起来。
image-20260522175239547

下载模型

配置环境

1
2
uv add transformer huggingface_hub
uv add torch torchvision torchaudio --index https://download.pytorch.org/whl/cu128
1
2
3
4
5
6
7
8
9
10
11
12
13
import os

# 1. 必须最先设置环境变量(在 import huggingface_hub 之前)
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

# 2. 然后再导入 Hugging Face 的下载工具
from huggingface_hub import snapshot_download

# 3. 最后执行下载
snapshot_download(
repo_id="deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
local_dir="models/DeepSeek-R1-Distill-Qwen-1.5B"
)

加载模型

先查看模型文件夹下的config.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{
// 模型的架构类名,告诉 Transformers 库在代码中应该用哪个具体的类来实例化这个模型
"architectures": [
"Qwen2ForCausalLM"
],

// 在注意力机制(Attention)层中 Dropout 的比例。0.0 代表不随机丢弃神经元节点
"attention_dropout": 0.0,

// 序列开始特殊标记(BOS, Begin of Sequence)的 ID 编码
"bos_token_id": 151643,

// 序列结束特殊标记(EOS, End of Sequence)的 ID 编码。模型吐出这个 ID 时就会停止生成
"eos_token_id": 151643,

// 隐藏层(前馈神经网络)中使用的激活函数类型。这里采用的是 SiLU(Sigmoid Linear Unit)
"hidden_act": "silu",

// 隐藏层的维度(模型的宽度)。即每个 Token 进入模型内部后转换成的向量长度(1536维)
"hidden_size": 1536,

// 权重参数初始化时的标准差。模型刚创建、未训练时,权重会在这个范围的随机正态分布中生成
"initializer_range": 0.02,

// 前馈神经网络(FFN/MLP)内部升维后的中间层维度。向量会在这里从 1536 临时扩大到 8960 维再缩回
"intermediate_size": 8960,

// 模型支持的最大位置编码长度。也就是该模型的理论最大上下文窗口(128K Context Window)
"max_position_embeddings": 131072,

// 启用滑动窗口注意力机制(Sliding Window Attention)的最大层数。这里前 21 层会受到相关策略影响
"max_window_layers": 21,

// 模型的类型基础名称,这里注册为千问2代架构(qwen2)
"model_type": "qwen2",

// 多头注意力机制(MHA)中 Query(查询)头的总数量
"num_attention_heads": 12,

// 模型的总层数(深度)。数据需要连续穿过 28 个 Transformer 块(Block)
"num_hidden_layers": 28,

// 分组查询注意力(GQA)中 Key 和 Value 头的数量。
// 12 个 Q 头共享 2 个 KV 头,能大幅减少推理时的 KV Cache 显存占用
"num_key_value_heads": 2,

// RMSNorm(均方根归一化)层为了防止分母为 0 而引入的极小常量值($\epsilon$)
"rms_norm_eps": 1e-06,

// 旋转位置编码(RoPE)的底数基频($\theta$)。控制位置编码在长文本下的外推和衰减敏感度
"rope_theta": 10000,

// 当启用滑动窗口注意力时,窗口的具体大小(限制只能看到过去 4096 个 Token)
"sliding_window": 4096,

// 是否绑定输入和输出的嵌入层权重。false 代表输入 Embedding 和输出 Linear 层的参数各自独立
"tie_word_embeddings": false,

// 模型训练和默认保存时的数据精度类型。bfloat16 代表 16位脑浮点数,兼顾范围与精度
"torch_dtype": "bfloat16",

// 导出该配置文件时,所使用的官方 Hugging Face Transformers 库的版本号
"transformers_version": "4.44.0",

// 是否默认开启键值缓存(KV Cache)。开启后可以避免重复计算历史 Token,实现流式快速生成
"use_cache": true,

// 是否启用多模态旋转位置编码(Multimodal RoPE),常用于处理视觉等多模态输入。这里为 false
"use_mrope": false,

// 是否在全局强制启用滑动窗口注意力机制。这里为 false,说明默认使用全量的全局注意力
"use_sliding_window": false,

// 词表的大小。代表模型一共认识 151,936 个不同的字、词或特殊符号
"vocab_size": 151936
}

在这个文件里:

  • "architectures": ["Qwen2ForCausalLM"]
  • "model_type": "qwen2"

说明这个模型底层架构为qwen2因果解码器

image-20260523110036536
1
2
3
4
5
6
7
from transformers import Qwen2ForCausalLM
model = Qwen2ForCausalLM.from_pretrained("models/DeepSeek-R1-Distill-Qwen-1.5B")
model

from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("models/DeepSeek-R1-Distill-Qwen-1.5B")
model

下面的AutoModelForCausalLM可以通过读取config文件,使用正确的架构加载模型

什么是ForCausalLM(因果语言模型)

ForCausalLM(因果语言模型)—— 只能看左边,猜右边

这种模型在训练和推理时,就像是在看一部正在播放的电影

通俗例子:

假设训练数据是一句话:“我 喜欢 吃 南京 盐水鸭”

当模型处理到 “吃” 这个字时:

  • 它能看到什么“我 喜欢”(左边的历史信息,它100%看得见)。
  • 它被隐藏了什么[南京 盐水鸭](右边的未来信息。模型被戴上了特殊的“向后看”的眼罩,技术上叫因果掩码,强行把右边的字遮住)。
  • 它的任务:根据 “我 喜欢 吃”,去猜后面接 “南京” 的概率是多少。

为什么必须隐藏后面的? 因为如果训练时不把右边的 “南京 盐水鸭” 挡住,模型就会直接去偷看标准答案,这就成了“作弊”。这样训练出来的模型在实际聊天时,由于没有未来可以偷看,就会彻底废掉。

ForMaskedLM(掩码语言模型)—— 整句话全看见,做完形填空

这就对应了你说的“一整句话都能看到”的模型。它在训练时不需要戴单向眼罩。

通俗例子:

同样是这句话:“我 喜欢 吃 南京 盐水鸭”

AI 老师在训练它时,直接把整句话拍在它面前,但是用黑笔随机涂黑(Mask)了一个词:

  • 输入给模型的“我 喜欢 吃 [ ❌ ] 盐水鸭”
  • 它能看到什么:它能同时看到左边的 “我 喜欢 吃” 和右边的 “盐水鸭”。整句话的上下文它尽收眼底。
  • 它的任务:像做高考完形填空一样,结合两边的线索,推测被涂黑的 [ ❌ ] 里面有高概率是 “南京”

qwen与deepseek架构的区别

image-20260523100747237
image-20260523100939383

区别一:前馈网络(FFN)的“大一统” vs “MoE 混合专家”

这是两家最本质的区别。

  • Qwen(Dense 稠密流派): Qwen2.5-72B 采用的是标准的 Dense 结构。这意味着无论是算 1+1= 这种简单的算术题,还是写复杂的 Linux 内核代码,模型在每一层里的前馈网络(FFN)都是全员上阵。720亿参数全部参与计算。
    • 优缺点:对硬件非常友好,算子成熟,但在参数量极大时,计算成本(算力消耗)呈线性飙升。
  • DeepSeek(MoE 专家流派): DeepSeek-V3 采用了独创的 DeepSeekMoE 架构。它把原本一个巨大的 FFN 拆分成了 1个共享专家(Shared Expert)256个路由专家(Routed Experts)
    • 独特设计:当一个 Token 传过来时,共享专家永远参与计算(抓取全局共性知识),而路由专家中只有最对口的 2 个会被激活(抓取垂直专业知识)。
    • 恐怖的性价比:DeepSeek-V3 的总参数量高达 6710亿(671B),但因为 MoE 的存在,每个 Token 进来时实际上只激活了 370亿(37B) 参数。这就是为什么它能用极低的算力成本,抗衡甚至超越其他几千亿规模的稠密模型。

区别二:注意力机制的“GQA” vs “MLA(低秩多头注意力)”

在处理上下文和长文本记忆(KV Cache)时,两家拿出了不同的看家本领。

  • Qwen(采用 GQA): 正如我们在它的 config.json 里看到的,Qwen 采用的是 GQA(Grouped-Query Attention)。它让多个 Query 头共用一组 KV 头。这在 2024~2026 年是行业标准设计,已经能把显存占用砍掉一大截。
  • DeepSeek(独创 MLA): DeepSeek 觉得 GQA 还不够省。他们发明了 MLA(Multi-head Latent Attention,低秩多头注意力机制)
    • 硬核原理:传统的 GQA 随着上下文变长,显存里的 KV 缓存依然会线性增加。而 MLA 在计算注意力时,通过矩阵低秩分解(Low-rank Compression)技术,把原本巨大的 KV 矩阵压缩成了一个极小的“特征向量”(Latent Vector)存进显存里。在真正计算的一瞬间,再在显存里动态把它解压放大恢复出来。
    • 效果:DeepSeek-V3 的 MLA 技术,让它的 KV Cache 显存占用直接暴降了 93%。这意味着同样的显卡硬件,DeepSeek 可以容纳超出 Qwen 数倍的并发量(Batch Size)或更长的上下文交互。

加载分词器

1
2
3
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("models/DeepSeek-R1-Distill-Qwen-1.5B")
tokenizer

可以查看models-R1-Distill-Qwen-1.5B.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"model": {
// 分词器(Tokenizer)采用的核心算法。
// "BPE" 代表 Byte-Pair Encoding(字节对编码),是目前大模型(如 GPT、Llama、Qwen)最主流的分词算法
"type": "BPE",

// 在分词阶段随机丢弃 Token 的比例(用于某些分词器的训练或正则化)。
// null 代表关闭该功能,确保分词结果是完全确定且唯一的
"dropout": null,

// 未知词标记(Unknown Token)。当遇到词表里完全不认识的字符时,会用这个符号代替。
// null 代表没有设置或不需要(在现代 BPE 分词器中,通常通过字节回退机制来彻底消灭未知词)
"unk_token": null,

// 持续子词前缀。如果一个词被切碎了,后续的碎片可以用这个前缀来标记它们属于同一个词。
// 空字符串 "" 代表不显式添加前缀(Qwen2/Llama 通常通过在 Token 内部保留空格空间来处理词边界)
"continuing_subword_prefix": "",

// 词尾后缀。用来标记一个分词片段是一个完整单词的结尾。
// 空字符串 "" 代表不使用特定的词尾后缀符号
"end_of_word_suffix": "",

// 是否把连续出现的未知词(UNK)融合合并成一个。
// false 代表不融合,每个不认识的字符单独处理
"fuse_unk": false,

// 字节回退机制开关。
// 核心技术点:当设置为 true 时,如果遇到词表里没有的生僻字(比如特殊的emoji),
// 分词器不会报错,也不会把它变成 UNK,而是直接把它拆解成底层的 UTF-8 字节(Byte)符号来兜底,确保全字符覆盖。这里为 false。
"byte_fallback": false,

// 核心词表(Vocabulary 字典)。它是一个庞大的映射表,规定了“文本片段”到“数字 ID”的唯一对应关系
"vocab": {
"!": 0, // 文本中的感叹号,映射到模型内部的数字 ID 是 0
"\"": 1, // 文本中的双引号(经过了转义),映射到模型内部的数字 ID 是 1
"#": 2 // 文本中的井号,映射到模型内部的数字 ID 是 2
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
inputs = tokenizer(
[
"Hello, how are you?", # 输入的第一句话(较短,包含 5 个 Token)
"I'm doing well, thank you!", # 输入的第二句话(较长,包含 7 个 Token)
],

# 1. 填充策略(Padding)
# 作用:强制对齐批次(Batch)内所有句子的长度,让它们变成规整的矩阵(行、列对齐)。
padding=True,

# 2. 返回数据类型(Return Tensors)
# 作用:"pt" 代表 PyTorch Tensor。直接将结果打包成 PyTorch 的张量矩阵,不需要手动转换,方便直接 `.to("cuda")` 送进显卡。
return_tensors="pt",

# 3. 填充方向(Padding Side)—— 本次最核心的修改!
# 作用:强行规定把用来凑数的补零标记([PAD])加在短句子的“左侧”(开头位置)。
# 核心技术原因(大模型推理的铁律):
# 现代大语言模型是自回归生成(从左到右逐字吐字)。
# 如果把 [PAD] 放在右边(默认行为),模型生成新字时,它看到的最新上文就会是 [PAD],这会彻底破坏位置编码(RoPE)以及 Attention 的最新特征,导致模型直接开始胡言乱语或者卡死。
# 把 [PAD] 放在左边,能确保所有句子的“最右端”(即接下来要吐新字的位置)在数学矩阵上完美对齐,而且模型能无缝读到最新的有效上文。
padding_side="left"
).to(model.device)

inputs
image-20260523134035248

padding的作用

模型会把多个句子打包成一个批次(Batch)同时塞给模型同时预测的,但是批量计算要求输入必须是一个完美的矩形矩阵(行和列的数量必须严格相等),所以在句子长度不一的时候,需要设置padding,也就是填充[PAD]

1
2
行1 ──> [   你 ,   叫 ,   什么 ,   名字 ,   呀 ,   ? ]  (原本就最长,无需Padding)
行2 ──> [ [PAD], [PAD], [PAD], [PAD], 南京 , 大学 ] (短句,左边被垫了 4 个 PAD)

分词器还会同时生成一个 attention_mask 矩阵,防止Attention 难被无意义的 [PAD] 干扰

1
2
行1 ──> [ 1, 1, 1, 1, 1, 1 ]  (全看)
行2 ──> [ 0, 0, 0, 0, 1, 1 ] (前4个PAD被强制致盲,Attention 算矩阵时直接把它们当空气)
image-20260523134330284

Padding Side的作用

既然有attentionmask了,为什么还要填充方向(),填充到右边不也行吗

致命原因一:位置编码(RoPE)完全错乱

现代大模型(如 Qwen2、DeepSeek)使用的是 旋转位置编码(RoPE)。模型必须要知道每个 Token 在句子里的绝对位置(它是第几个字),才能理解语法。

假设有两句话同时预测,最大对齐长度是 6:

  • 句子 A 只有 3 个字:“真 漂 亮”
  • 句子 B 有 6 个字:“南京 师范 大学”

❌ 如果填充在右边(Right Padding):

1
2
3
【第1步:输入矩阵】
行 1 ──> [ 真(pos:0) , 漂(pos:1) , 亮(pos:2) , [PAD](pos:3) , [PAD](pos:4) , [PAD](pos:5) ]
行 2 ──> [ 南(pos:0) , 京(pos:1) , 师(pos:2) , 范(pos:3) , 大(pos:4) , 学(pos:5) ]

第一步计算时,有 attention_mask 挡着,句子 A 只看了前 3 个字,成功预测出下一个字是 “啊”

但是,接下来模型要同时生出第 2 个字了(自回归下一步):

新生成的字 “啊” 必须拼到句子的末尾。

  • 句子 B 的 “学” 在位置 5,所以新生成的字在 位置 6
  • 句子 A 的 “亮” 在位置 2,新生成的字 “啊” 应该在 位置 3

灾难发生了:在矩阵计算中,显卡必须整齐划一地把新字拼在矩阵的最后一列(位置 6)

这导致句子 A 的 “啊” 被强行赋予了 位置 6 的位置编码!

在模型眼里,这个句子变成了:“真(0) 漂(1) 亮(2) ...空了3个位置... 啊(6)”。位置编码的断层会直接让自注意力机制彻底报废,模型开始疯狂吐胡话。

如果填充在左边(Left Padding):

1
2
3
【第1步:输入矩阵】
行 1 ──> [ [PAD](pos:0) , [PAD](pos:0) , [PAD](pos:0) , 真(pos:1) , 漂(pos:2) , 亮(pos:3) ]
行 2 ──> [ 南(pos:1) , 京(pos:2) , 师(pos:3) , 范(pos:4) , 大(pos:5) , 学(pos:6) ]

通过特殊的处理,左边的 [PAD] 位置编码直接被无视。

重点看右边:句子 A 的末尾 “亮” 在位置 3,句子 B 的末尾 “学” 在位置 6。

当模型同时吐出新字时,它们都可以规整地统一拼在矩阵的右侧边框(也就是下一列),各自的位置编码能够丝滑地递增(位置 3 变 4,位置 6 变 7),绝对不会发生空间断层。

致命原因二:自回归推理的“长文本记忆(KV Cache)”无法对齐

大模型为了单字蹦字变快,内部有一套叫 KV Cache 的机制——它会把前面已经算过的词的 KV 矩阵存存在显存里,下一次算新字时,直接复用旧的,不重新计算。

显卡在批量(Batch)计算时,要求每一行的记忆长度必须是一致的

❌ 如果 Padding 在右边:

1
2
行 1 (有效记忆3个) ──> [ K1, K2, K3, [PAD], [PAD], [PAD] ] ──> 接下来新字在这里 💥 撞墙了
行 2 (有效记忆6个) ──> [ K1, K2, K3, K4 , K5 , K6 ] ──> 接下来新字在这里

当自回归进行到第二步、第三步时,最新的 Token 需要不断追加到 KV 缓存的末尾。

第一行的末尾被 [PAD] 占着坑,新词进不来;如果强行覆盖 [PAD],第一行的有效长度只有 3,第二行有效长度是 6,这导致 KV 缓存矩阵的各行长度不一致

显卡的核心在面对这种“参差不齐”的动态矩阵时,根本无法做并行的矩阵乘法

如果 Padding 在左边:

1
2
行 1 ──> [ [PAD], [PAD], [PAD], K1, K2, K3 ] ──> 最新插入点 ──> [这里完美对齐]
行 2 ──> [ K1 , K2 , K3 , K4, K5, K6 ] ──> 最新插入点 ──> [这里完美对齐]

因为 [PAD] 稳稳地呆在左边底座,所有句子的动态增长前沿(右侧边缘)是完全对齐的

每蹦出一个新字,所有的句子都可以整齐划一地向右集体拓宽一列。显存里的 KV Cache 矩阵始终是一个标准的、完美的矩形,显卡可以用 O(1) 的极高效率并行吞吐。

模型推理

1
2
3
4
5
6
7
output=model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
max_new_tokens=50
)

output

使用模型进行推理后面的向量

1
tokenizer.batch_decode(output)

解码翻译成人类语言

但是目前模型的回答是这样

1
2
["<|end▁of▁sentence|><|end▁of▁sentence|>Hello, how are you? I'm trying to understand some concepts about the electromagnetic spectrum. Could you explain the different parts of it and their relationships? Also, what's the significance of each part?\n\nCertainly! The electromagnetic spectrum is a range of all possible wavelengths of electromagnetic radiation.",
"I'm doing well, thank you! What is the sum of 1/3 and 1/6? Let me think. Hmm, okay, so I need to add these two fractions together. I remember that to add fractions, they need to have the same denominator. The denomin"]

很明显是有问题的,为什么呢

image-20260523135418262

它在预训练阶段(图中的第 1 步)学到的本领是自回归续写(盲猜下一个字)。它习惯了看到普通文章,就顺着普通文章的语法往下编。

但是,现代的对话/推理模型(如 DeepSeek-R1、Qwen2.5-Instruct)是通过微调(图中的第 2 步)训练出来的。为了让它知道什么时候该“闭嘴”、什么时候该“思考(<think>)”、什么时候该“回答”,官方在训练它时,强行给它定了一套严格的特殊符号包围圈

如果你直接把普通的字符串 "Hello, how are you?" 喂给它,对它来说格式是完全陌生的(它找不到应有的标记)。

添加标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
message=[
[
{"role": "user", "content": "Hello, how are you?"}
],
[
{"role": "assistant", "content": "I'm doing well, thank you!"}
]
]
tokenizer.padding_side="left"

inputs=tokenizer.apply_chat_template(
message,
return_tensors="pt",
padding=True,
add_generation_prompt=True
).to(model.device)

tokenizer.batch_decode(inputs["input_ids"])

tokenizer.apply_chat_template是调用models-R1-Distill-Qwen-1.5B_config.json中的chat_template Jinja2 模板,将对话添加标签

Jinja2 本质上是 Python 编程世界里最著名、使用最广泛的 “文本自动化渲染引擎”(Template Engine)

在代码层面上,它的工作公式非常纯粹:

$$\text{Jinja2 模板 (死标签)} \quad + \quad \text{Python 变量 (活数据)} \quad \xrightarrow{\text{Render (渲染)}} \quad \text{最终的纯文本字符串}$$

image-20260523140200484
1
2
['<|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|begin▁of▁sentence|><|User|>Hello, how are you?<|Assistant|><think>\n',
"<|begin▁of▁sentence|><|Assistant|>I'm doing well, thank you!<|end▁of▁sentence|><|Assistant|><think>\n"]

add_generation_prompt=True会在最后添加<|Assistant|><think>\n用来引导模型生成

1
2
3
4
5
6
7
output=model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
max_new_tokens=256
)

tokenizer.batch_decode(output)

最后再让模型推理,便能正确生成答案了

1
2
['<|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|begin▁of▁sentence|><|User|>Hello, how are you?<|Assistant|><think>\nAlright, the user greeted me with "Hello, how are you?" which is a friendly and open way to start a conversation.\n\nI should respond in a similar positive manner to keep the conversation going smoothly.\n\nI need to make sure my response is welcoming and inviting them to share what they\'re up to.\n\nKeeping it simple and open-ended is key here.\n</think>\n\nHello! I\'m doing well. How can I assist you today?<|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|><|end▁of▁sentence|>',
'<|begin▁of▁sentence|><|Assistant|>I\'m doing well, thank you!<|end▁of▁sentence|><|Assistant|><think>\nOkay, so the user just said, "I\'m doing well, thank you!" Hmm, I need to figure out what they\'re really looking for here. Let me break it down.\n\nFirst, they said, "I\'m doing well, thank you!" That\'s pretty straightforward. They\'re acknowledging their effort and appreciation. But maybe they want more than that? Maybe they\'re feeling a bit overwhelmed or just looking for reassurance.\n\nI should consider different scenarios. Could they be feeling down because of something recent? Or maybe they\'re feeling a bit isolated? Sometimes people appreciate a simple thank you but need more support or a different perspective.\n\nI wonder if they\'re asking for tips on how to handle situations or if they need advice on how to improve. It\'s possible they\'re seeking encouragement or motivation. Alternatively, they might be seeking reassurance that they\'re not alone in feeling this way.\n\nAnother angle is that they might be looking for a way to express gratitude in a different way. Maybe they want to change their approach to communication. Or perhaps they\'re dealing with a specific challenge that\'s making them feel inadequate, and they need validation.\n\nI should also think about the tone they\'re using. They said, "I\'m doing well, thank you!" which is positive and']

为什么会蹦出这么多 <|end▁of▁sentence|>

大模型提前说完了话,它会吐出一个终止符,也就是 <|end▁of▁sentence|>(意为“句子结束”)。

但是,由于这是批量推理,第一句话的通道虽然无话可说了,但第二句话的通道还没写完,显卡不能停,必须硬着头皮继续往后算满剩下的步数。

为了保持矩阵的绝对规整,显卡在接下来的每一步里,只能在第一句的通道后面疯狂重复填充“终止符”来凑数

1
2
3
4
【显卡最终吐出的 output 矩阵物理外观】

第一行 (短句通道) ──> [ Hello! ... assist you today? , <|end...> , <|end...> , <|end...>, ... ] (后面全在机械重复填终止符)
第二行 (长句通道) ──> [ I'm doing well... (模型正兴高采烈地写到一半) ]

因为显卡矩阵在右侧必须对齐,短句子通道在“无话可说”到“等长句子写完”之间的所有空白格,全部被强行塞满了 <|end▁of▁sentence|>

参考资料

本地部署大模型!用Transformers库跑通DeepSeek-R1_哔哩哔哩_bilibili

二分查找

二分查找为什么总是写错?_哔哩哔哩_bilibili

image-20260521135449487

例题:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

注意边界问题

  1. 空数组问题:如果 nums = []n = 0。循环不执行,binary_search_l 会检查 nums[0]binary_search_r 会检查 nums[-1],两者都会直接抛出 IndexError
  2. target 大于数组中所有元素:例如 nums = [1, 2], target = 3binary_search_l 循环结束后 right 会等于 n(即 2),此时检查 nums[right] 会抛出 IndexError
  3. target 小于数组中所有元素:例如 nums = [1, 2], target = 0binary_search_r 循环结束后 left 会等于 -1。在 Python 中 nums[-1] 不会报错而是取最后一个元素,这会导致逻辑错误(如果数组为空则依然会报错)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def searchRange(nums: list[int], target: int) -> list[int]:
n = len(nums)
# 边界问题 1:提前处理空数组
if n == 0:
return [-1, -1]

def binary_search_l(left, right, target):
while left + 1 != right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid
else:
right = mid
# 边界问题 2:right 可能越界到达 n,需要先判断 right != n
if right != n and nums[right] == target:
return right
return -1

def binary_search_r(left, right, target):
while left + 1 != right:
mid = (left + right) // 2
if nums[mid] <= target:
left = mid
else:
right = mid
# 边界问题 3:left 可能越界到达 -1,需要先判断 left != -1
if left != -1 and nums[left] == target:
return left
return -1

# 寻找左边界
left_idx = binary_search_l(-1, n, target)

# 优化:如果左边界都找不到,说明 target 不存在,直接返回,无需再找右边界
if left_idx == -1:
return [-1, -1]

# 寻找右边界(此时可以缩小左边界范围,从 left_idx - 1 开始找,提升微小性能)
right_idx = binary_search_r(left_idx - 1, n, target)

return [left_idx, right_idx]

流派一:暴力排除法(找具体的值)

核心思想mid 被检查后,它绝对不是最终答案(或者它就是答案,我们已经直接 return 了)。既然不是答案,就必须把它狠狠踢出搜索范围。

  • 适用场景:在有序数组中找一个具体的数(比如 target)。
  • 更新方式left = mid + 1right = mid - 1。(一定要 +1-1 跨过 mid
  • 循环条件left <= right。(因为当 left == right 时,区间里还有最后一个元素,你需要进循环检查它是不是答案)。
  • 初始边界left = 0, right = n - 1。(答案只可能在数组的索引范围内)。

标准模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def binary_search_1(nums, target):
left, right = 0, len(nums) - 1

while left <= right: # 注意是 <=
mid = (left + right) // 2

if nums[mid] == target:
return mid # 找到了,直接返回
elif nums[mid] < target:
left = mid + 1 # mid 不是答案,踢掉它
else:
right = mid - 1 # mid 不是答案,踢掉它

return -1


流派二:温柔缩圈法(找边界 / 找位置)

核心思想mid 被检查后,它有可能就是我们要找的那个边界!所以不能把它踢掉,只能让搜索区间慢慢向它“靠拢”。

  • 适用场景:找第一个大于/等于 target 的数,或者找最后一个小于/等于 target 的数。(比如你之前写的 searchRange 找左右边界)。
  • 更新方式left = midright = mid。(绝对不能 +1-1,否则会把正确答案踢掉)。
  • 循环条件left < right。(当 left == right 时,区间缩成了一个点,这个点必然就是答案,不需要再进循环了)。
  • 初始边界:通常 left = 0, right = n。(答案有可能在数组外面,比如所有数都小于 target,答案就是 n)。

2.1 标准版模板(容易死循环,需小心)

1
2
3
4
5
6
7
8
9
10
11
def binary_search_2_standard(nums, target):
left, right = 0, len(nums)

while left < right: # 注意是 <
# 【防死循环核心】:如果后面有 left = mid,这里必须 +1 向上取整!
mid = (left + right + 1) // 2

if check(mid): # 假设满足条件时,答案在 mid 左边或就是 mid
right = mid # mid 可能是答案,保留它,缩小区间
else:
left = mid + 1 # mid 确定不是答案,踢掉

dfs

给新手的回溯法总结: 1)当组合中允许元素重复:则迭代的起始值就是i,每次递增 2)当组合中不允许重复:迭代的起始值是i + 1,每次递增 3)当这是排列不是组合:迭代的起始值是传进去的start(初始位),每次都从头开始遍历所有元素

组合

77. 组合 - 力扣(LeetCode)

n 个不同的元素中取出 k 个元素,不考虑选出的顺序,只关心“最终选到了哪些元素

image-20260726142117371
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void dfs(int n, int k,int cur){
if(path.size()+n-cur+1<k) return;
if(path.size()==k){
ans.push_back(path);
return;
}
for(int i=cur;i<=n;i++){
path.push_back(i);
dfs(n,k,i+1);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
dfs(n,k,1);
return ans;
}
};

排列

n 个不同的元素中取出 k 个元素,严格考虑选出的顺序。相同的元素,只要顺序不同,就是不同的排列。

https://leetcode.cn/problems/permutations?envType=study-plan-v2&envId=top-interview-150

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<int> visited;
void dfs(int n,vector<int> &nums){
if(n==nums.size()){
ans.push_back(path);
return;
}
for(int i=0;i<nums.size();i++){
if(visited[i]) continue;
path.push_back(nums[i]);
visited[i]=1;
dfs(n+1,nums);
path.pop_back();
visited[i]=0;
}
}
vector<vector<int>> permute(vector<int>& nums) {
visited = vector<int>(nums.size(), 0);
dfs(0,nums);
return ans;
}
};

邻接表建图+bfs

399. 除法求值 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
double bfs(string src,string dst,unordered_map<string,unordered_map<string,double>> &mp){
if(!mp.count(src))return -1;
queue<pair<string,double>> q;
q.push({src,1.0});
unordered_set<string> visited;
visited.insert(src);
while (!q.empty())
{
pair<string,double> node=q.front();
q.pop();
string a=node.first;
double val=node.second;
if(a==dst){
return val;
}
for(auto &[neighbour,weight]:mp[a]){
if(!visited.count(neighbour)){
visited.insert(neighbour);
q.push({neighbour,weight*val});
}
}
}
return -1.0;

}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
unordered_map<string,unordered_map<string,double>> mp;
int i=0;
for(auto &equation:equations){
string a=equation[0],b=equation[1];
mp[a][b]=values[i];
mp[b][a]=1.0/values[i];
i++;
}
vector<double> ans;
for(auto &query:queries){
ans.push_back(bfs(query[0],query[1],mp));
}
return ans;
}
};

拓扑排序:判断有向图是否存在环

图-拓扑排序_哔哩哔哩_bilibili

Kahn 算法(基于“入度”的 BFS 拓扑排序)

入度(In-degree):一个节点被多少条有向边指向(即这门课需要多少门先修课)。

如果一门课的入度为 0,说明它不需要任何先修课,可以直接修读!

我们把所有入度为 0 的课放入队列。修完一门课后,把它指向的后继课程的“先修要求”去掉(后继课程的入度 -1)。

如果某门后继课程的入度减到了 0,说明它的先修课全修完了,也入队。

终局判断:最后统计修完的课程总数。如果等于 V,说明无环;否则说明图中存在互相依赖的“死锁环”。

207. 课程表 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses);
vector<int> inDegree(numCourses, 0);

for(auto &pre : prerequisites){
int a = pre[0];
int b = pre[1];
graph[b].push_back(a);
inDegree[a]++;
}

queue<int> q;
for(int i = 0; i < numCourses; i++){
if(inDegree[i] == 0){
q.push(i);
}
}

int count = 0;
while(!q.empty()){
int cur = q.front();
q.pop();
count++;

for(int neighbour : graph[cur]){
inDegree[neighbour]--;
if(inDegree[neighbour] == 0){
q.push(neighbour);
}
}
}

return count == numCourses;
}
};

排序

image-20260604103056976

快速排序

数据结构合集 - 快速排序(算法过程, 效率分析, 稳定性分析)_哔哩哔哩_bilibili

核心思想:

  1. 选择基准 (Pivot):从数列中挑出一个元素。
  2. 分区 (Partition):重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面。
  3. 递归排序 (Recursion):递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

如何理解排序的稳定性

  • 如果算法只进行相邻元素的比较和交换(如冒泡排序、插入排序、归并排序),它就能小心翼翼地保护相同元素的原始顺序,它是稳定的。
  • 如果算法为了追求速度,允许元素进行远距离的交换或覆盖(如快速排序、选择排序、堆排序),这种“粗暴的跨越”就不可避免地会打乱相同元素的原始顺序,它是不稳定的。

因此快排是不稳定的

复杂度分析:

最坏情况:当每次选取最大值或最小值作为pivot,时间复杂度退化为O(N^2)

最好情况:当数组随机时/每次选取pivot都可以将数组一分为二,时间复杂度位O(nlogn)

优化1:随机选 pivot

  • 作用:防止数组原本有序时退化

挖坑法:使用>=,如果出现大量相同元素,right会将相同元素再遍历一遍,此时退化成O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from typing import List
import random
def sortArray(nums: List[int]) -> List[int]:
def half_sort(nums,left,right):
#优化1:在 left 和 right 之间随机生成一个索引
index=random.randint(left, right)
nums[left],nums[index]=nums[index],nums[left]
pivot=nums[left]
while left<right:
# 1. 从右向左找,找到第一个小于 pivot 的数
while left < right and nums[right] >= pivot:
right -= 1
nums[left] = nums[right] # 把这个数填到左边的坑里,右边形成新坑

# 2. 从左向右找,找到第一个大于 pivot 的数
while left < right and nums[left] <= pivot:
left += 1
nums[right] = nums[left] # 把这个数填到右边的坑里,左边形成新坑

# 3. 当 left == right 时,循环结束,把 pivot 填入最后的坑中
nums[left] = pivot
return left

def quick_sort(nums,left,right):
if right-left<=0:
return
pivot_index=half_sort(nums,left,right)
quick_sort(nums,left,pivot_index-1)
quick_sort(nums,pivot_index+1,right)

quick_sort(nums,0,len(nums)-1)
return nums
if __name__ == '__main__':
print(sortArray([2,2,2,2]))
print(sortArray([5,1,1,2,0,0]))
image-20260603164311421
image-20260603164606684

Hoare 分区

i,j要先移动是防止相同元素交换后还是一样,造成死循环

保证i右边的数>=pivot,保证h左边的数<=pivot,当遇到不满足的数时进行交换

最后保证左区间所有元素 ≤ 右区间所有元素;

为什么能完美处理大量重复元素?

等于 pivot 的元素会被两个指针同时“抓住”,并通过交换被分散到左右两侧。指针持续向中间移动,最终 j 会落在接近中央的位置,左右子数组规模基本相等。即使数组中所有元素都相等,也能做到每次递归规模减半,递归深度 O(log n),时间复杂度 O(n log n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from typing import List
import random
def sortArray(nums: List[int]) -> List[int]:
def half_sort(nums,left,right):
index=random.randint(left, right)
nums[left],nums[index]=nums[index],nums[left]
pivot=nums[left]
i,j=left-1,right+1
while 1:
i+=1#i,j要先移动是防止相同元素交换后还是一样,造成死循环
while nums[i]<pivot:#保证i右边的数>=pivot
i+=1
j-=1
while nums[j]>pivot:#保证h左边的数<=pivot
j-=1
if i>=j:
return j
nums[i],nums[j]=nums[j],nums[i]

def quick_sort(nums,left,right):
if right-left<=0:
return
pivot_index=half_sort(nums,left,right)
quick_sort(nums,left,pivot_index)
quick_sort(nums,pivot_index+1,right)

quick_sort(nums,0,len(nums)-1)
return nums
if __name__ == '__main__':
print(sortArray([2,2,2,2]))
print(sortArray([5,1,1,2,0,0]))

归并排序

数据结构合集 - 归并排序(非递归与递归算法过程, 效率分析, 稳定性分析)_哔哩哔哩_bilibili

image-20260604104858667

归并排序的核心思想就是

  1. 分 (Divide):将数组从中间一分为二,分别对左右两个子数组进行排序。
  2. 治 (Conquer):递归地将子数组排序。
  3. 合 (Combine):将两个已排序的子数组合并成一个有序数组。

空间复杂度 O(n),时间复杂度 O(n log n),稳定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from typing import List
import random
def sortArray(nums: List[int]) -> List[int]:
ans=[0]*len(nums)
def merge_sort(nums,left,right):
if left>=right:return

mid=(left+right)//2
merge_sort(nums,left,mid)
merge_sort(nums,mid+1,right)
index=left
i,j=left,mid+1
while i<=mid and j<=right:
if nums[i]>nums[j]:
ans[index]=nums[j]
index+=1
j+=1
else:
ans[index]=nums[i]
index+=1
i+=1
if i<=mid:
ans[index:right+1]=nums[i:mid+1]
else:
ans[index:right+1]=nums[j:right+1]
nums[left:right+1]=ans[left:right+1]
merge_sort(nums,0,len(nums)-1)

return ans

问:代码中的 nums[i] < pivot 和 nums[i] > pivot 能否改成 nums[i] <= pivot 和 nums[i] >= pivot?

答:这个做法会在子数组所有元素相同时,划分后的 j 是子数组最后一个元素的下标,是最不均匀划分,算法会退化至 O(n**2)。

问:代码中的 i <= j 能否改成 i < j?

答:这会算错。来看一个例子 nums=[2,1,3],pivot=2。左指针 i=1 移动到 i=2,右指针 j=2 因为不满足 i < j 的条件,无法移动。此时我们交换 2 和 nums[j]=3,得到 [3,1,2],返回 j=2。然而 j=2 左侧有大于 pivot=2 的元素,划分失败。

如果写成 i <= j,那么最终 i=2,j=1。此时我们交换 2 和 nums[j]=1,得到 [1,2,3],返回 j=1。这样的划分就是正确的。

堆排序

数据结构合集 - 堆与堆排序(算法过程, 效率分析, 稳定性分析)_哔哩哔哩_bilibili

image-20260623120621616
image-20260623121353399

不稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:

def heap(index,nums,n):
#维护操作,比较左右节点是否都小于根节点,然后递归
left=index*2+1
right=index*2+2
maxindex=index

# if left<n and nums[left]>nums[index]:
# nums[left],nums[index]=nums[index],nums[left]
# heap(left,nums)
# 先找左右节点哪个最大
if left<n and nums[left]>nums[maxindex]:
maxindex=left
if right<n and nums[right]>nums[maxindex]:
maxindex=right
if maxindex!=index:
#说明有变化,换位置,然后递归
nums[maxindex],nums[index]=nums[index],nums[maxindex]
heap(maxindex,nums,n)
def heap_sort(nums):
n=len(nums)
#建堆,从最后一个非叶子节点开始,往下维护大根堆
for i in range(n//2-1,-1,-1):
heap(i,nums,n)
#排序,建堆后,第一个位置一定最大,移动到最后,然后再对第一个建堆维护
for i in range(n-1,0,-1):
nums[0],nums[i]=nums[i],nums[0]
heap(0,nums,i)
return nums

return heap_sort(nums)

堆排序就是反复建堆的过程是吗,但是最小堆的数据结构,不需要维护数组有序,只要保证第一个元素是最小的就行,大根堆就是维护第一个元素最大

  • 堆的性质:你理解得很对,局部有序,全局无序,只保堆顶。
  • 堆排序的本质建一次堆(O(N)) + 反复调整堆(N次 O(log N)) = 总时间复杂度 O(N log N)

堆的方法

  • 上浮 (Sift Up) 专门用于:插入新元素 (heappush)
  • 下沉 (Sift Down) 专门用于:删除堆顶 (heappop)批量建堆 (heapify)

heappush是在列表后面新加一个元素,也就是加一个叶子节点,这个叶子节点要以此查看自己的父亲节点,维护堆的性质,称之为上浮

heappop是弹出最大/最小元素,其实是弹出arr[0],,但左右子树依旧是保持着堆的性质,那其实思路就是和最后一个叶子节点交换位置,然后利用数组的pop弹出,再进行一次下沉操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class minheap:
def __init__(self,arr=None):
self.heap=[]
if arr:
self.heapify(arr)

def __heapdown(self,index:int,n:int):
left=index*2+1
right=index*2+2
minindex=index
if right<n and self.heap[right]<self.heap[minindex]:
minindex=right
if left<n and self.heap[left]<self.heap[minindex]:
minindex=left
if minindex!=index:
self.heap[minindex],self.heap[index]=self.heap[index],self.heap[minindex]
self.__heapdown(minindex,n)

def heapify(self,arr:list):
self.heap=arr[:]
n = len(self.heap)
# 从最后一个非叶子节点开始,逆序遍历,依次下沉
for i in range(n // 2 - 1, -1, -1):
self.__heapdown(i, n)

def heappop(self):
if not self.heap: # ✅ 添加空堆检查
return None

if len(self.heap) == 1: # ✅ 只有一个元素时直接弹出
return self.heap.pop()
#先和最后一个元素交换,然后pop,再下沉操作
n=len(self.heap)-1
self.heap[0],self.heap[n]=self.heap[n],self.heap[0]
#下沉操作
self.__heapdown(0,n)# ✅ 修正:传入 n 而不是 n-1,这个n是数量,不是下标

#返回最大值并弹出
return self.heap.pop()
def heappush(self,num:int):
self.heap.append(num)
#进行上浮操作,以此比较父亲节点
index=len(self.heap)-1
while index>0:
parent=(index-1)//2
if self.heap[index]<self.heap[parent]:
self.heap[index],self.heap[parent]=self.heap[parent],self.heap[index]
index=parent
else:
break

二叉树

前中后序遍历

遍历方式 所属搜索类型 核心数据结构 遍历顺序特征 典型应用场景
前序遍历 DFS 栈 (Stack) / 递归 复制树、序列化、生成前缀表达式
中序遍历 DFS 栈 (Stack) / 递归 二叉搜索树(BST)的排序输出
后序遍历 DFS 栈 (Stack) / 递归 删除树、计算目录大小、生成后缀表达式
层序遍历 BFS 队列 (Queue) 从上到下,从左到右按层访问 求树的最小深度、Z字形打印、寻找最短路径
  • 前序(非递归):用栈。根节点入栈 →→ 弹出并访问 →→ 右子节点入栈 →→ 左子节点入栈(保证左先出)。
  • 中序(非递归):用栈。一路向左将节点压入栈 →→ 弹出并访问 →→ 转向右子树。
  • 后序(非递归):用栈。通常需要记录上一个访问的节点,或者使用“根 →→ 右 →→ 左”的顺序入栈,最后将结果数组反转,得到“左 →→ 右 →→ 根”。

前序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
#递归版
def preorderTraversal(root: Optional[TreeNode]) -> List[int]:
ans=[]
def dfs(node:TreeNode):
if not node:
return
ans.append(node.val)
if node.left:dfs(node.left)
if node.right:dfs(node.right)
dfs(root)
return ans
#栈
def preorderTraversal_stack(root: Optional[TreeNode]) -> List[int]:
ans=[]
stack=[]
stack.append(root)
while stack:
node:TreeNode=stack.pop()
if not node:
continue
ans.append(node.val)
if node.right:stack.append(node.right)#注意和dfs的差别
if node.left:stack.append(node.left)
return ans

中序遍历栈的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* curr = root;

// 当 curr 不为空 或 栈不为空 时继续
while (curr != nullptr || !st.empty()) {

// 第1步:一路向左,全部压栈
while (curr != nullptr) {
st.push(curr);
curr = curr->left;
}

// 第2步:弹出栈顶(最左节点),访问它
curr = st.top();
st.pop();
result.push_back(curr->val); // 访问节点

// 第3步:转向右子树
curr = curr->right;
}

return result;
}
};

层序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def levelOrder(root: Optional[TreeNode]) -> List[List[int]]:
ans=[]
queue=deque()
if not root:return ans
queue.append(root)
while queue:
n=len(queue)
temp=[]
for _ in range(n):
node:TreeNode=queue.popleft()
if not node:continue
temp.append(node.val)
if node.left:queue.append(node.left)
if node.right:queue.append(node.right)
ans.append(temp)
return ans

二叉树的构造(前序中序、后序中序、层序中序构造二叉树)

数据结构合集 - 二叉树的构造(前序中序、后序中序、层序中序构造二叉树)_哔哩哔哩_bilibili

image-20260605101610954
image-20260605130441466
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def buildTree(preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not preorder and not inorder:
return None
hashmap={}
for index,val in enumerate(inorder):
hashmap[val]=index
n=len(preorder)
def dfs(in_left,in_right,pre_left,pre_right):
if in_right-in_left<0:
return None
pivot_index=hashmap[preorder[pre_left]]
node=TreeNode()
node.val=preorder[pre_left]
size=pivot_index-in_left
node.left=dfs(in_left,pivot_index-1,pre_left+1,pre_left+size)
node.right=dfs(pivot_index+1,in_right,pre_left+size+1,pre_right)

return node
root:TreeNode=dfs(0,n-1,0,n-1)


return root

二叉搜索树

对于树中的任意一个节点,必须同时满足以下三个条件:

  1. 左小:它左子树上的所有节点值,都严格小于它自己的值。
  2. 右大:它右子树上的所有节点值,都严格大于它自己的值。
  3. 递归:它的左子树和右子树,也必须分别是二叉搜索树。
1
2
3
4
5
6
7
    8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13

考点 1:中序遍历 = 升序数组(最重要!)

对 BST 进行中序遍历(左 -> 根 -> 右),得到的结果一定是一个从小到大排好序的数组! * 上面那棵树的中序遍历:1, 3, 4, 6, 7, 8, 10, 13, 14。 * 应用:很多题目让你找 BST 的第 K 小元素、验证是不是 BST、找两个节点的差值最小,全部用中序遍历解决

考点 2:查找/插入/删除不需要遍历全树

普通二叉树找东西要遍历所有节点(O(N)),但 BST 只需要顺着“左小右大”的路径走,不需要回溯,一路走下去就行(O(H),H是树高)。

考点 3:警惕“退化成链表”

如果插入的数据本来就是有序的(比如 1, 2, 3, 4, 5),BST 会变成这样:

1
2
3
4
5
6
7
1
\
2
\
3
\
4
这时候它退化成了链表,查找速度变成了 O(N)(为了解决这个问题,大佬们发明了“平衡二叉搜索树”,如 AVL树、红黑树。C++ 里的 setmap 底层就是红黑树。)

完全二叉树

类型 定义 形状 节点数公式
满二叉树 (Full/Perfect) 每一层都填满了,最后一层也是满的 完美的三角形 2h − 1
完全二叉树 (Complete) 上面都满了,最后一层可以不满,但必须靠左 缺了右下角的三角形 无固定公式,但在 2h − 12h − 1 之间

C++ 里的 priority_queue(优先队列),底层就是用完全二叉树实现的,而且它通常不用指针(TreeNode*)来存,而是用数组(vector)来存

数组下标的魔法公式(假设根节点下标为 i):

  • 左孩子的下标是:2 * i + 1
  • 右孩子的下标是:2 * i + 2
  • 父节点的下标是:(i - 1) / 2
image-20260627113832645

模式匹配

kmp

最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili

帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili

nextval 数组是 KMP 算法的究极优化体。它通过在预处理时加入一步判断,消除了模式串中连续重复字符导致的无意义回溯。当字符发生错配时,nextval 能让指针一步跳跃到真正有效的下一个截然不同的待匹配字符,将最坏情况下的回溯开销降到了极致。”

python

字符串

split()

将字符串按照指定的分隔符“切割”,并返回一个列表(List)

1
str.split(sep=None, maxsplit=-1)
  • sep:分隔符(字符串)。如果不传,默认按空白字符(空格、换行符 \n、制表符 \t 等)分割。
  • maxsplit:最大分割次数。如果不传,默认 -1(表示不限制次数,有多少切多少)。

join()

是把列表(或任何可迭代对象)“拼”成字符串

1
2
3
4
chars = ['H', 'e', 'l', 'l', 'o']
word = "".join(chars)
print(word)
# 输出: Hello

isdigit()

判断一个字符串是否“完全由数字字符组成”。 如果是,返回 True;只要包含一个非数字字符(或者字符串为空),就返回 False

栈,队列

deque

dequedouble-ended queue(双端队列)的缩写

deque 专门为两端的高效操作进行了优化,在头部和尾部添加、删除元素的速度都非常快,都是 O(1) 的时间复杂度

1
from collections import deque
  • append(x):从右边(尾部) 添加元素。
  • appendleft(x):从左边(头部) 添加元素。
  • pop():从右边(尾部) 移除并返回元素。
  • popleft():从左边(头部) 移除并返回元素。

random 模块

random.randint(a, b):生成一个 [a, b] 范围内的随机整数(包含 a 和 b)。

heapq 是 Python 的内置标准库模块,用于实现堆(Heap)*数据结构,特别适合做*优先队列

  1. 默认是最小堆(Min Heap):堆顶永远是最小元素
  2. 基于列表实现:直接在普通 list 上操作
  3. 时间复杂度:插入和删除都是 O(log n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import heapq
# 方法1:空列表 + heappush
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 4)
print(heap) # 输出: [1, 3, 4] 堆顶是最小值 1

# 方法2:heapify 快速建堆
nums = [3, 1, 4, 1, 5, 9, 2, 6]
heapq.heapify(nums) # 原地转换,O(n)

# 弹出最小值 O(log n)
min_val = heapq.heappop(heap)
函数 说明 时间复杂度
heappush(heap, item) 插入元素 O(log n)
heappop(heap) 弹出最小值 O(log n)

cpp

在 C++ 中,建立最小堆(Min-Heap)最常用的工具是标准库 <queue> 中的 priority_queue(优先队列)。

默认情况下,priority_queue最大堆(大的元素在顶端)。要变成最小堆,你需要修改它的模板参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <queue>
#include <vector>
#include <functional> // 包含 greater

// 语法:priority_queue<数据类型, 容器类型, 比较方式>
priority_queue<int, vector<int>, greater<int>> minHeap;

// 1. 定义一个比较结构体
struct cmp {
// 注意:这里的参数类型要和堆里存的一样,是 ListNode*
// 返回 true 表示 a 的优先级比 b 低(a 应该沉下去,b 浮上来)
bool operator()(ListNode* a, ListNode* b) {
return a->val > b->val;
}
};

// 2. 声明最小堆
// 参数1: 存的数据类型 (ListNode*)
// 参数2: 底层容器 (vector<ListNode*>)
// 参数3: 刚才写的比较规则 (cmp)
priority_queue<ListNode*, vector<ListNode*>, cmp> minHeap;

万能头文件

1
#include <bits/stdc++.h>

结构体

1
2
3
4
5
6
7
struct node
{
int val;
int index1;
int index2;
node(int v,int i1,int i2):val(v),index1(i1),index2(i2){}
};

{}的用法:初始化对象 / 容器;函数传参(上下文推导)

1
2
3
4
5
6
7
8
9
10
// 1. 容器
vector<int> v = {1, 2, 3}; // 列表初始化
map<string, int> m = {{"A", 1}}; // 嵌套花括号初始化键值对

// 2. 结构体 / 聚合类(所有成员 public)
struct Point { int x; int y; };
Point p = {10, 20}; // ✅ 完美匹配

// 3. std::pair / std::tuple
pair<int, string> p = {1, "hello"}; // ✅ 合法,编译器自动转成 pair
1
2
3
4
5
6
7
8
9
10
queue<pair<TreeNode*, int>> q;

// ❌ 错误:不能直接 auto 接收
auto temp = {root, root->val};

// ✅ 正确:push 函数要求 pair,编译器看到目标类型,自动把 {} 转换成 pair
q.push({root, root->val});

// ✅ 正确:make_pair 明确要求返回 pair
auto temp = make_pair(root, root->val);

auto用法

拯救冗长的 STL 类型

1
2
3
4
5
6
7
priority_queue<pair<int, pair<int, int>>, vector<...>, cmp> pq;

// 不用 auto(折磨):
pair<int, pair<int, int>> top_element = pq.top();

// 用 auto(清爽):
auto top_element = pq.top(); // 编译器自动推导

遍历容器

1
2
3
4
5
6
vector<string> names = {"Alice", "Bob"};

// 爽!直接遍历
for (auto name : names) {
cout << name << endl;
}

结构化绑定

1
2
3
4
5
queue<pair<TreeNode*, int>> q;
q.push({root, root->val});

// 爽!直接解包(注意这里是方括号 [],不是花括号 {})
auto [node, current_sum] = q.front();

哈希表

unordered_map(键值对哈希表)

相当于 Python 的 dict。用来存 Key -> Value 的映射。

1
2
3
4
5
6
7
#include <unordered_map>
using namespace std;

// 语法:unordered_map<键的类型, 值的类型> 变量名;
unordered_map<int, string> myMap; // 键是 int,值是 string
unordered_map<string, int> wordCount; // 键是 string,值是 int
unordered_map<char, vector<int>> charMap; // 键是 char,值是 vector

增 & 改(最常用)

使用 [] 运算符是最简单直接的方式。

1
2
3
4
5
unordered_map<int, int> map;

map[1] = 100; // 新增:键为 1,值为 100
map[2] = 200; // 新增:键为 2,值为 200
map[1] = 999; // 修改:键 1 已存在,值被覆盖为 999

查(判断键是否存在)

这是刷题时最高频的操作,主要有两种写法:

写法 A:使用 count()(最推荐,简单直观) count(key) 会返回 1(存在)或 0(不存在)。

1
2
3
4
5
if (map.count(1)) {
cout << "键 1 存在!" << endl;
} else {
cout << "键 1 不存在!" << endl;
}

写法 B:使用 find()(返回迭代器) find(key) 会返回一个迭代器。如果找不到,它会返回 map.end()

1
2
3
if (map.find(1) != map.end()) {
cout << "键 1 存在!" << endl;
}

1
map.erase(1); // 删除键为 1 的键值对

遍历操作

在 C++ 中,遍历 unordered_map 通常使用范围 for 循环(配合 auto)。 遍历时,每一个元素都是一个 pair(键值对),可以通过 .first 获取键,.second 获取值。

1
2
3
4
5
6
unordered_map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};

// 推荐写法:使用 const auto& 避免拷贝,提高效率
for (const auto& pair : scores) {
cout << "姓名: " << pair.first << ", 分数: " << pair.second << endl;
}

(注意:unordered_map无序的,遍历出来的顺序不一定是你插入的顺序!)

vector

insert()

1
vec.insert(插入位置的迭代器, 插入的数量, 要插入的值);

初始化

1
2
3
4
vector<类型> 变量名(元素个数, 初始值);
vector<int> v(10, 0);

vector<int> v = {1, 2, 3, 4, 5};

互联网的基础与组成

核心概念

路由器 (Router)

  • 功能:连接两个或多个不同的计算机网络(如将家庭网络连接到外部互联网)。
  • 作用:它是实现“互联网”连接的关键设备,负责在不同网络之间转发数据。

交换机 (Switch)

  • 功能:将多个网络节点(如电脑、电视、手机)连接起来,组成一个局部的计算机网络。
  • 应用场景:常用于家庭、公司或学校内部组建局域网
image-20260512092631854

ISP

全称:Internet Service Provider(互联网服务提供商)。

通俗理解:它是连接普通用户与互联网的“中介”或“运营商”。如果没有 ISP,我们的手机和电脑就无法访问广域网。

组成部分

image-20260512094913137

功能

image-20260512095248915

电路交换报文交换

1. 电路交换 (Circuit Switching)

这是一种类似于传统打电话的交换方式,必须先“占线”才能通信。

  • 优点:
    • 传输速率高、数据直达: 通信前会建立一条端到端的专用物理通路。
    • 适用场景: 更适合低频次、大量的数据传输(就像打电话一样,拨号花点时间,但一旦接通,长时间通话体验很好)。
  • 缺点:
    • 时间开销大: 建立连接和释放连接都需要消耗额外的时间。
    • 利用率低: 在整个通信期间,线路被双方独占,即使中间有一方没在发数据,别人也用不了这根线。
    • 灵活性差: 线路分配不够灵活,且非常不适合计算机之间常见的“突发式”通信(即高频次、少量的数据传输)。

2. 报文交换 (Message Switching)

这种方式不需要事先占线,而是把整个数据块(报文)传给相邻节点,节点存下来之后再找下一步的路,也就是“存储转发”。

  • 优点:
    • 无需建立连接: 随时有数据随时发,省去了建立连接的时间。
    • 线路利用率高、分配灵活: 用户无需独占整条线路,节点采用“存储转发”的方式动态分配线路。
    • 支持差错控制: 交换节点可以通过校验技术检查数据是否传错。
  • 缺点:
    • 管理不便: 报文的长度是不固定的,这给节点的存储和转发管理带来了麻烦。
    • 资源和时间开销大: 如果报文很长(比如图里手写的 80GB 传到 8bit 链路上),节点需要极大的缓存空间来接收它,并且整体的存储转发时间开销很大
    • 重传代价高: 长报文如果在传输中出现了一点点错误,整个长报文都需要重新传输,代价极其高昂

分组交换

image-20260512101440274
  1. 发送端:拆分与装箱 (拆)

当用户(如小帅 H1)要发送一份很大的“用户数据”给接收者(如小美 H5)时:

  • 切割:数据不会整块发送,而是被切成一个个更小的单位,称为分组 (Packet)
  • 加首部:每个分组前面都会加上“首部”(类似快递单),上面写着源 IP 地址目的 IP 地址
  • 网络中:存储转发 (送)

图中绿色的“分组交换机”(本质上就是路由器)起到了中转站的作用:

  • 存储:路由器先接收整个分组并暂时存入缓存。
  • 查表:读取分组首部的目的地址,查询自己的“路由表”。
  • 转发:决定走哪条路(比如 A→B 或 A→C)最快,然后把分组发往下一个路由器。
  • 特点:每个分组独立选择路径,不一定要走同一条路。
  • 接收端:还原与合并 (合)

当所有分组到达目的地(如小美 H5)后:

  • 剥离:去掉每个分组的首部(撕掉快递单)。
  • 排序重组:按照分组编号,把散乱的数据重新拼装成原始的“用户数据”。
  1. 发送端:拆分与装箱 (拆)

当用户(如小帅 H1)要发送一份很大的“用户数据”给接收者(如小美 H5)时:

  • 切割:数据不会整块发送,而是被切成一个个更小的单位,称为分组 (Packet)
  • 加首部:每个分组前面都会加上“首部”(类似快递单),上面写着源 IP 地址目的 IP 地址
  • 网络中:存储转发 (送)

图中绿色的“分组交换机”(本质上就是路由器)起到了中转站的作用:

  • 存储:路由器先接收整个分组并暂时存入缓存。
  • 查表:读取分组首部的目的地址,查询自己的“路由表”。
  • 转发:决定走哪条路(比如 A→B 或 A→C)最快,然后把分组发往下一个路由器。
  • 特点:每个分组独立选择路径,不一定要走同一条路。
  • 接收端:还原与合并 (合)

当所有分组到达目的地(如小美 H5)后:

  • 剥离:去掉每个分组的首部(撕掉快递单)。
  • 排序重组:按照分组编号,把散乱的数据重新拼装成原始的“用户数据”。

一、 分组交换的核心优点

  • 无需建立连接:发送数据前不需要像打电话(电路交换)那样先拨号,数据直接发出,实时性好
  • 线路利用率高:多个用户的数据可以同时在一条物理线路上交替传输,不会独占资源,非常省钱高效。
  • 灵活分配与容错:路由器根据网络拥堵情况动态选择路径。如果某条路断了,分组可以自动绕道,且支持差错控制,发现坏数据能及时纠正。

二、 横向对比:为什么比“报文交换”好?

报文交换是把整封邮件发出去,而分组交换是拆成小件发。

  • 管理更方便:分组有固定长度上限,路由器更容易管理内存和缓存。
  • 速度更快:由于分组小,路由器处理单个分组的存储转发时间极短,减少了排队等待。
  • 容错代价低:如果传输中出错了,只需要重传出错的那一小块(分组),而不需要重传整个大数据包。

三、 存在的缺点与挑战

  • 额外开销大:每个小分组都要贴上“快递单”(控制信息/首部),当数据被拆得太碎时,这些首部信息的占比会显著增加,降低了纯数据的传输效率。
  • 存在时延:每个路由器都要执行“存储-检查-转发”的过程,这会产生一定的处理时延
  • 处理复杂性
    • 失序:小件快递走不同路径,可能导致“后发的先到”。
    • 丢失:某个分组可能在路上弄丢了。
    • 重复:可能收到两个一样的分组。
    • *注:这些问题都需要接收端的主机通过复杂的协议逻辑来重新排序和处理

计算机网络的分类


网络类型 全称 覆盖范围 典型应用场景 特点/技术
广域网 (WAN) Wide Area Network 几十 ~ 几千公里 跨省、跨国、跨洲 覆盖范围最广,是互联网的核心
城域网 (MAN) Metropolitan Area Network 几千米 ~ 几十千米 一个或几个相邻城市 常采用以太网技术,常并入局域网讨论
局域网 (LAN) Local Area Network 几十米 ~ 几千米 学校、企业、家庭 范围较小,通常使用以太网技术
个域网 (PAN) Personal Area Network 几十米以内 个人设备连接 常通过无线技术连接(如蓝牙),又称WPAN
image-20260512105302052

性能指标

image-20260512112200680

1. 信道 (Channel)

  • 概念: 指向某一个方向传送信息的通道。
  • 核心要点: 信道 通信线路。一条物理上的通信线路(比如你家插在路由器上的网线),在逻辑上通常包含一条发送信道一条接收信道。这也是为什么我们测速时,会有“上行速率”和“下行速率”两个不同的指标。

2. 速率 (Speed / Data Rate)

  • 概念: 网络节点在信道上每秒传输的数据量,也叫数据率或比特率。
  • 单位: bps (bit/s 或 b/s)。
  • ⚠️ 超级易错点(单位与进位):
    • B 与 b 的区别: 大写 B 代表字节 (Byte),小写 b 代表比特 (bit)。1B = 8b
    • 网络领域的进位(十进制): 在计算机网络中衡量速率时,前缀 k、M、G、T 的进位是 103
      • 1 kbps = 103 bps
      • 1 Mbps = 106 bps
    • 体系结构/操作系统的进位(二进制): 在衡量内存、硬盘等存储容量时,前缀 K、M、G、T 的进位是 210
      • 1 KB = 210 B = 1024 B

结合幻灯片的例子: 你家办理了“千兆宽带”,运营商口中的“千兆”指的是 1000 Mbps(也就是 109 bps)。用迅雷下载文件时,软件显示的单位通常是 MB/s (兆字节/秒)。

所以理论最高下载速度 = 1000 Mbps ÷ 8 = 125 MB/s

3. 带宽 (Bandwidth)

带宽这个词在不同学科里有两个截然不同的含义:

  • 在《计算机网络》中(数字信号):
    • 含义: 指某信道所能传送的最高数据传输速率(即速率的上限)。
    • 单位: bps (bit/s)。
  • 在《通信原理》中(模拟信号):
    • 含义: 指某信道允许通过的信号频带范围(最高频率与最低频率之差)。
    • 单位: Hz (赫兹)。

1. 时延 (Delay) - 【计算题重灾区】

数据从网络一端传送到另一端所需的总时间。它由四个部分拼凑而成:总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延

  • 发送时延 (也叫传输时延): 把数据从节点“推”到信道上花的时间。
    • 公式: 数据长度 (bit) ÷ 发送速率 (bit/s)
    • 比喻: 就像火车(数据)驶出站台(网卡)所需的时间。火车越长,驶出站台的时间越久。
  • 传播时延: 数据在物理信道上“跑”完这段距离花的时间。
    • 公式: 信道长度 (m) ÷ 电磁波传播速度 (m/s)
    • 比喻: 火车完全驶出站台后,在铁轨上开到终点站的时间。只和铁轨有多长、火车开多快有关。
  • ⚠️ 超级易混点: 考试时一定要分清这两个!提高网速(带宽)只能减少“发送时延”,无论你办千兆还是万兆宽带,信号在光缆里的“传播时延”(光速)是物理定律,改变不了的。
  • (注:处理时延和排队时延一般在路由器内发生,受网络拥堵影响,基础计算题中如无特别说明常忽略不计。)
image-20260512133345314

2. 时延带宽积 (Delay-Bandwidth Product)

  • 公式: 传播时延 × 带宽
  • 物理含义: 想象信道是一根圆柱形的空心管道,传播时延是管道的“长度”,带宽是管道的“截面积”。时延带宽积就是这个管道的体积
  • 通俗理解: 它代表发送端连续发送数据时,第一个比特刚好到达接收端时,链路上“正在飞”的最大比特数(即管道被塞满时能装多少数据)。

3. 往返时延 (RTT, Round-Trip Time)

  • 含义: 从发送方发送完数据,到发送方收到接收方的“确认回复”,总共经历的时间。我们在电脑上 ping 某个网站弹出的时间就是 RTT。
  • 包含哪些部分:
    • 去程的:单向传播时延
    • 接收方的:处理时延 + 发送时延 (把确认包推上信道)
    • 回程的:传播时延 (确认包跑回来的时间)
  • ⚠️ 易错陷阱: RTT 不包含 发送方发送业务数据本身的“发送时延” (t1)!它只从数据“发完”的那一刻开始掐表。
image-20260512134726893

4. 信道利用率

  • 含义: 信道有多大比例的时间是在传数据的(没有空闲)。
  • 辩证关系:
    • 太低:修了八车道高速公路却没几辆车,浪费资源。
    • 太高:容易导致网络拥塞。就像节假日的高速公路,利用率接近 100% 时,车会堵死,排队时延会急剧增加

分层结构

1. 数据的传输视角:水平与垂直

理解网络体系结构,需要同时具备“水平”和“垂直”两种思维:

  • 水平视角(逻辑通信): 每一层的实体(比如两台电脑的应用层)都认为自己在和对端的同一层进行直接通信。它们之间交流的规则就是“协议”。为了让对端看懂自己的意图,发送方会在数据前加上专门的“首部”(有时还有“尾部”)。
  • 垂直视角(物理实现): 数据真正在主机内部的流动是自上而下(发送方)和自下而上(接收方)的。发送方每往下一层,就套上一层新的“首部”(这叫封装,像套娃一样);到了最底层的物理层,所有数据变成单纯的 01 比特流在网线里跑;接收方则逐层剥离首部(这叫解封装),把原始数据还原给上层。
    • 注意图中的小细节: 只有第2层(数据链路层)不仅加了首部 H2,还加了尾部 T2
image-20260512143350054
image-20260512143200180

2. 三个重要的数据单元名词 (SDU, PCI, PDU)

这是考试中最爱考概念辨析的地方,你需要记住它们的全称和包含关系:

  • SDU (Service Data Unit, 服务数据单元): 上一层传下来的、需要本层帮忙传输的原始数据(Payload)
  • PCI (Protocol Control Information, 协议控制信息): 本层为了实现协议功能而自己添加的“首部/尾部”信息
  • PDU (Protocol Data Unit, 协议数据单元): 本层最终打包好的、准备传给下一层的完整数据块

核心换算公式:

n 层的完整数据块,等于本层加的控制信息加上层传下来的数据:

n-PDU = n-PCI + n-SDU

n 层打包好的数据,到了第 n − 1 层眼里,统统变成了需要传输的原始数据:

n-PDU = (n − 1)-SDU

image-20260512143215280

3. 协议的三要素

网络协议是水平的,它约束了通信双方必须遵守的规则。一个完整的协议必须包含以下三个要素:

  1. 语法 (Syntax): 规定了数据和控制信息的结构与格式。比如:报文的首部占多少个字节?第1个字节代表什么意思?
  2. 语义 (Semantics): 规定了通信双方需要“做什么动作”或“表达什么意思”。比如:这串代码是代表“请求建立连接”,还是代表“数据接收成功(ACK)”?
  3. 同步/时序 (Synchronization/Timing): 规定了事件实现的顺序和条件。比如:发送方发完数据后,如果在 10 秒内没收到对方的确认,就需要重新发送

OSI 七层参考模型

image-20260512154222954
层次 (从下到上) 英文简称 数据传输单位 核心任务与功能 典型网络设备
1. 物理层 Physical 比特 (Bit) 实现相邻节点间比特流的透明传输;定义接口的物理/电气特性(如电压代表0还是1)。 集线器 (Hub)、中继器
2. 数据链路层 Data Link 帧 (Frame) 确保相邻节点间的逻辑无差错;进行差错控制(校验、纠错/丢弃)、流量控制。 交换机 (Switch)、网桥
3. 网络层 Network 分组/数据报 (Packet) 将分组从源节点路由转发到目的节点;维护路由表、拥塞控制、网际互联。 路由器 (Router)
4. 传输层 Transport 报文段 (Segment) 实现端到端(进程到进程,认准“端口”)的通信;复用与分用;全局差错/流量控制。 -
5. 会话层 Session 报文 (Message) 管理进程间会话;使用“检查点”机制实现断点续传。 -
6. 表示层 Presentation 报文 (Message) 处理数据格式转换(如编码转换、数据压缩/解压加密/解密)。 -
7. 应用层 Application 报文 (Message) 实现特定的网络应用(如微信、浏览器等)。 主机 (端系统)

⚠️ 易混淆的“重灾区”提醒:

在这几张图中,老师用红笔重点圈出的几个细节,是考试最喜欢挖坑的地方:

1. 差错控制:数据链路层 vs 传输层

  • 数据链路层的差错控制是“局部”的,它只保证这段网线(相邻两个节点A到B)传的数据没错,错了就扔掉。
  • 传输层的差错控制是“全局”的,它要保证从发送方主机一路经过无数路由器,最终到达接收方主机的数据是完整、有序、不丢失的。

2. 设备的工作层次(图2)

  • 路由器是“三层设备”:它最高只能理解到网络层(拆开看分组首部里的 IP 地址)。
  • 交换机是“二层设备”:它最高只能理解到数据链路层(拆开看帧首部里的 MAC 地址)。
  • 集线器是“一层设备”:它就是个无脑的物理放大器,只管电信号的 0 和 1。
  • 只有你使用的手机和电脑(主机)才拥有完整的 1 到 7 层。

3. 通信范围的本质区别

  • 网络层(IP):解决的是“主机到主机”的通信(帮你把包裹送到收件人的小区)。
  • 传输层(端口):解决的是“进程到进程”的通信(不仅送到小区,还要具体交给收件人本人,比如是给微信的还是给 QQ 的)。
image-20260512154120967

TCP/IP 模型

image-20260512164948826

1. 模型结构对比

  • OSI 参考模型(7层):应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
  • TCP/IP 模型(4层):应用层、传输层、网络层、网络接口层。
  • 层级映射关系
    • TCP/IP 的应用层整合了 OSI 的应用层、表示层(数据格式转换)和会话层(会话管理)。如果应用需要格式转换或会话管理,由应用自身实现。
    • TCP/IP 的网络接口层整合了 OSI 的数据链路层和物理层。TCP/IP 并没有具体规定这一层的功能和协议,而是交由网络设备商自由发挥。

2. 各层的数据传输单位与核心功能

  • 应用层(单位:报文):实现特定的网络应用。
  • 传输层(单位:报文段):实现端到端(进程到进程)的通信。负责复用和分用、差错控制、流量控制、连接管理等。
  • 网络层(单位:数据报/分组):把分组从源结点转发到目的结点。核心功能是路由选择分组转发
  • 数据链路层(单位:帧):确保相邻节点之间的链路逻辑上无差错(主要负责差错控制、流量控制)。
  • 物理层(单位:比特):实现相邻节点之间的比特流传输,定义电路接口参数、电气特性等。

3. TCP/IP 模型中的“背锅”机制(网络层与传输层的协作)

课件通过生动的比喻(“摆烂”和“背锅”)解释了 TCP/IP 的设计理念:

  • TCP/IP 的网络层(摆烂):只提供“尽最大努力交付”,数据传输是不可靠的。它只管路由和转发,不负责差错控制和流量控制。
  • TCP/IP 的传输层(背锅):因为网络层不可靠,所以保证数据传输正确性和可靠性的重任就交给了传输层。传输层必须负责差错控制、流量控制和可靠传输管理。

4. 核心考点总结:OSI vs TCP/IP 服务的差异

最后一张图片总结了两个模型在网络层和传输层提供服务时的关键区别,这是考试/面试的重点:

比较维度 OSI 参考模型 TCP/IP 模型
网络层服务 可提供无连接不可靠服务(数据报),也可提供有连接可靠服务(虚电路)。 提供无连接不可靠的服务(数据报/尽最大努力交付)。
传输层服务 向应用层提供有连接的可靠服务。 可提供有连接可靠服务(TCP协议),也可提供无连接不可靠服务(UDP协议)。

总结一句话: OSI 模型在设计时希望网络层能做更多可靠性的工作,而传输层相对单纯;TCP/IP 模型则极其务实,让底层的网络层“轻装上阵”(不可靠、无连接),将复杂的可靠性保障全部推给了上面的传输层去实现。

物理层

通讯原理基本概念

1. 信号的分类

  • 数字信号:信号的值是离散的(如常见的方波,用高低电平表示 0 和 1)。
  • 模拟信号:信号的值是连续的(如连续波动的正弦波)。
QQ20260512-164906

2. 通信系统的基本模型(五大要素)

  • 数据:信息的实体(如文字、声音、图像),在计算机内部通常以二进制表示。
  • 信号:数据的载体。数据必须转换为信号才能在信道中传输。
  • 信源:信号的来源(即数据的发送方,如计算机 A)。
  • 信宿:信号的“归宿”(即数据的接收方,如路由器或计算机 B)。
  • 信道:信号传输的通道。注意:一条物理线路通常包含两条信道(发送信道和接收信道),以实现双向通信。
QQ20260512-164922

3. 码元(Symbol)的概念

  • 定义:在一个“信号周期”内出现的某一种特定状态的信号,称为一个码元。
  • 多进制码元
    • 如果一个周期内信号有 4 种可能的取值(如 4 种不同的电压:2V, 1V, -1V, -2V),则称为 4进制码元,每个码元可以携带 2 个比特(2 bit)的数据(对应 00, 01, 10, 11)。
    • 同理,如果信号有 8 种状态,则是 8进制码元,每个码元携带 3 bit 数据。
  • 模拟信号设计:不仅数字信号可以设计多进制,模拟信号也可以通过改变波的特征(如不同的频率或相位组合)来设计出 2进制、4进制甚至更多进制的码元。
  • 优缺点:采用多进制码元的优点是每个信号周期能传输更多信息,提升传输效率;代价是需要更强的信号功率,且对信道质量(抗干扰能力)要求更高。
QQ20260512-165615

4. 核心公式:码元与比特的关系

这是计算网络传输速率的重要考点,描述了一个码元可以携带多少比特的数据:

  • 公式:如果一个信号周期内可能出现 K 种信号(即 K 进制码元),则:

    1 码元 = log2K bit

  • 举例

    • 4 种信号 (K = 4):log24 = 2 bit
    • 8 种信号 (K = 8):log28 = 3 bit
    • 32 种信号 (K = 32):log232 = 5 bit

奈氏准则(无噪声情形)和香农定理(有噪声情形)

1. 带宽(Bandwidth)的两种语境

虽然本质都是指“信道传输数据的能力”,但在不同学科中定义和单位有所不同:

  • 在《计算机网络》中:表示信道所能通过的“最高数据率”。单位是 bps(比特/秒)。
  • 在《通信原理》中:表示信道允许通过的信号频带范围。单位是 Hz(赫兹)。

2. 噪声与信噪比(SNR)

  • 噪声:会对信道产生干扰,从而影响数据的传输效率。
  • 信噪比:信号平均功率与噪声平均功率的比值。有两种表示方式:
    • 无单位记法$S/N = \frac{\text{信号的功率}}{\text{噪声的功率}}$
    • 分贝(dB)记法信噪比(dB) = 10log10(S/N)

3. 奈奎斯特定理(奈氏准则)—— 针对“无噪声”信道

该定理用于计算在理想低通信道(没有噪声、带宽有限)下的极限传输速率。

  • 极限波特率(码元传输速率):2W (单位:波特/Baud,即 码元/秒)

    • W 是信道的频率带宽(单位:Hz)。
  • 极限比特率(数据传输速率):结合“1个码元可携带 log2K bit 数据”的公式,推导出极限比特率为:

    2Wlog2K (b/s)

    • K 是一个“信号周期”内可能出现的信号状态种数(即多少进制的码元)。

4. 香农定理 —— 针对“有噪声”信道

该定理用于计算在实际信道(有噪声、带宽有限)下的极限传输速率。

  • 极限比特率公式

    Wlog2(1 + S/N) (b/s)

    • W 是信道的频率带宽(单位:Hz)。
    • S/N 是信噪比。
  • ⚠️ 核心易错点提示:在使用香农定理计算时,公式中的信噪比 S/N 必须使用“无单位记法”的比值形式。如果题目给出的是分贝(dB),必须先通过公式逆运算换算成无单位的比值后,才能代入香农公式计算。

编码与调制

QQ20260512-222444
image-20260519103945343
QQ20260512-221124
image-20260519104003426

传输介质

image-20260513084325475
image-20260513084342463
image-20260513084358459
image-20260513084435913

物理层设备

1. 中继器(Repeater)的工作原理

  • 核心功能:中继器只有两个端口。它的主要作用是接收衰减、失真的信号,将其整形再生(恢复成标准的电平信号),然后再转发到另一个端口,从而延长物理传输距离。
    • 示例:如果标准低电平是 0.5~1.5V,中继器收到失真的低电平信号后,会将其重新整形为标准的 1V 再输出。
  • 通信限制:仅支持半双工通信,即两端的节点不能同时发送数据,否则会导致信号“冲突”。
  • 网段概念:中继器的两个端口对应两个不同的“网段”。

2. 集线器(Hub)与冲突域

  • 本质:集线器可以看作是一个“多端口的中继器”。
  • 冲突域(碰撞域):连接在同一个集线器上的所有主机处于同一个冲突域。这意味着如果两台主机同时发送数据,信号就会在集线器内部发生冲突。
  • 信道争用:处于同一冲突域的主机,在发送数据前必须进行“信道争用”(抢占发送权)。

3. 集线器组网的关键特性

  • 拓扑结构差异
    • 物理上是“星形”拓扑:各台主机通过网线汇聚连接到中心的集线器上。
    • 逻辑上是“总线型”拓扑:数据在集线器内部是“广播式”传输的(发给一个节点,其他节点都能收到),工作机制与早期共享一根总线的网络完全一样。
  • 共享带宽:集线器连接的所有设备共享该集线器的总带宽。
    • 示例:如果一个集线器的总带宽是 10Mbps,连接了 8 台主机同时工作,那么每台主机平均只能分到 1.25Mbps 的带宽。

4. 中继器与集线器的通用限制

  • 不能“无限串联”:为了保证信号的有效传输和冲突检测,串联数量有严格限制。
    • 5-4-3 原则(以 10Base5 网络为例):最多只能串联 5 个网段,中间使用 4 个集线器(或中继器),并且这 5 个网段中只有 3 个网段可以挂接计算机。
  • 速率向下兼容:如果一个集线器连接了不同速率的网段(或网卡),会导致整个网络的所有网段强制“向下兼容”,以最低的那个速率运行。
  • 介质兼容性:集线器两端可以连接不同的传输介质,这意味着它连接的网段在物理层面的接口特性(物理层协议)是可以不同的。
image-20260513085749075

数据链路层

功能

1. 数据链路层所处的地位

  • 核心作用:数据链路层位于网络层和物理层之间,负责将网络层交下来的IP数据报(分组)加上首部和尾部,封装成帧
  • 服务关系
    • 向下:使用物理层提供的“比特传输”服务。
    • 向上:为网络层提供服务,负责将封装好的帧传输给下一个相邻结点(如从主机传到路由器)。
  • 两个核心概念辨析
    • 物理链路:由底层传输介质(0层)和物理层(1层)共同实现,是相邻结点间单纯的物理连接线路。
    • 逻辑链路(数据链路):数据链路层基于“物理链路”,通过相关协议实现了相邻结点间逻辑上无差错的数据传输通道。

2. 数据链路层的功能(五大核心)

第二张图以思维导图的形式概括了数据链路层必须完成的具体任务:

  • 封装成帧(组帧)
    • 帧定界:确保接收方能够准确识别出一个完整帧的界限(哪里开始,哪里结束)。
    • 透明传输:接收方的链路层能从收到的帧内无损恢复原始数据(SDU),让上方的网络层“感受不到”底层曾把数据切分并封装成帧的过程。
  • 差错控制
    • 专门用于发现并解决帧内部的“位错”(即具体的0、1比特发生翻转)。
    • 两种解决思路:一是接收方检错后丢弃该帧,让发送方重传(需检错编码);二是由接收方直接发现并自行纠正错误(需纠错编码)。
  • 可靠传输
    • 专门用于发现并解决宏观的“帧错”,包含三种典型情况:
      • 帧丢失:例如发 1234,只收到 124。
      • 帧重复:例如发 1234,收到了 12334。
      • 帧失序:例如发 1234,收到了 1324。
  • 流量控制
    • 控制发送方发送帧的速率,防止发送过快导致接收方“来不及”接收和处理。
  • 介质访问控制
    • 广播信道:必须实现此功能。因为广播信道逻辑上是总线型拓扑,多个结点需要“争抢”传输介质的使用权,必须有机制来协调。
    • 点对点信道:通常不需要此功能,因为两点之间有专属的传输介质,不存在信道争用问题。
QQ20260513-090143

组帧

1. 字符计数法

  • 原理:在每个帧的开头,使用一个固定长度的数字字段来表示该帧的总长度(注意:帧长 = 计数字段长度 + 数据部分长度)。
  • 最大缺陷(健壮性差):如果传输过程中由于干扰导致计数字段出错(例如原本长度是7变成了3),接收方就会找错边界,不仅当前帧报废,还会导致后续所有帧都无法正确定界,产生连锁反应。
image-20260513092318404

2. 字节填充法(特殊字符法)

  • 帧定界:使用特殊的控制字符来标记帧的开始(如 SOH - Start of Header)和结束(如 EOT - End of Transmission)。
  • 透明传输:如果帧的数据部分恰好包含了与 SOHEOT 相同的比特序列,发送方会在这些特殊字符前面插入一个“转义字符 ESC”。接收方在读取时如果遇到 ESC,就会知道紧跟其后的字符是普通数据,并在处理时将 ESC 删掉(逆向处理)。
image-20260513092345270

3. 零比特填充法

  • 帧定界:使用特殊的比特串 01111110 作为标记帧开始和结束的标志。
  • 透明传输(核心规则)
    • 发送方:对数据部分进行扫描,每当遇到连续的 5 个 1,就强制在后面填充一个 0。这样就绝对避免了数据段出现连续 6 个 1(即伪造的定界符)。
    • 接收方:对收到的数据部分进行逆处理,每当遇到连续的 5 个 1,就自动删掉紧随其后的 1 个 0,从而恢复原始数据。
  • 应用:HDLC协议和PPP协议均采用此方法。
image-20260513092358876

4. 违规编码法

  • 原理:利用物理层传输中“不合法的(违规)”信号电平来表示帧的开头和结尾。这种方法需要物理层的配合。
  • 示例:在标准的曼彻斯特编码中,每个比特周期的中间必须有电平跳变(上跳表示0,下跳表示1)。如果人为制造一个“周期中间不跳变”的信号,这就是一个违规信号,接收方就可以用这个特殊的不跳变信号来作为帧的定界符
image-20260513092412375

检错编码

这些图片是一份关于计算机网络数据链路层差错控制(检错编码)*的详细教学课件,重点讲解了两种常见的检错码:**奇偶校验码**和*循环冗余校验码(CRC)

以下是核心知识点的详细总结:

1. 奇偶校验码 (Parity Check)

  • 基本原理:在原有的“有效信息位”附加 1 位的“奇偶校验位”,使得整个校验码中数字“1”的个数满足奇数或偶数的要求。
    • 奇校验:整个校验码中“1”的个数为奇数。
    • 偶校验:整个校验码中“1”的个数为偶数。
  • 硬件实现(以偶校验为例):将各信息位进行“异或(模2加)”运算(即相同为0,相异为1),得到的结果就是偶校验位的值。
  • 致命缺陷:如果传输过程中发生位错的比特数是偶数个(比如2位、4位同时翻转),奇偶性不会改变,此时无法检测出错误
image-20260513110906416
image-20260513110915033

2. 循环冗余校验码 (CRC, Cyclic Redundancy Check)

CRC 是一种检错能力极强的编码方式,广泛应用于实际网络中。

  • 核心思想:发送方和接收方事先约定一个“除数”。发送方在数据后面添加若干位“校验位”(即余数),使得拼接后的完整数据能够被这个约定的“除数”整除。接收方收到数据后,用同样的“除数”去除,如果余数为 0,说明传输无误;若余数非 0,说明数据出错。
  • 运算规则:CRC 的计算使用的是“模2除法”,其本质是按位进行异或运算(不发生进位和借位)。

💡 CRC 计算的四大步骤(以课件例题为例):

例:生成多项式 G(x) = x3 + x2 + 1,信息码为 101001

  1. 确定“除数”和位数 R

    • 将生成多项式的系数提取出来,转换为二进制“除数”。x3 + x2 + 1 对应二进制为 1101
    • 确定校验位的长度 RR 等于生成多项式的最高次幂,即 R = 3(除数位数减1)。
  2. 移位(补零)

    • 将信息码左移 R 位,即在信息码后面补上 R 个 0,作为“被除数”。本例中变为 101001000
  3. 相除(模2除法)

    • 用被除数 101001000 模2除以除数 1101
    • 经过一步步异或运算,最后得到的 R 位余数即为 CRC 校验码(FCS)。本例中余数为 001。4
    image-20260513110952903
  4. 生成发送帧与检错

    • 发送方:将余数拼接在原始信息后,实际发送的数据为 101001001

    • 接收方:收到数据后,用 1101 进行模2除法,若最终余数为 000,则代表没有出错。

image-20260513111052010

3. CRC 的检错能力与特点

  • 理论上,只要生成多项式选择得当,CRC 可以检测出:所有奇数个错误、所有双比特错误、以及所有长度小于等于校验位长度的连续错误。
  • 在特定条件下(如 2R ≥ K + R + 1),CRC 码可以具备纠正 1 位错的能力,但在计算机网络的实际应用中,CRC 一般只用来“检错”,发现错误直接丢弃重传,而不进行复杂的纠错
image-20260513111109881

海明校验码(Hamming Code)

1. 海明码的核心设计思路与长度计算

  • 设计原理:将信息位进行分组,并为每组提供一个偶校验位。通过多个校验位的组合,不仅能检测出错误,还能直接“指引”出发生错误的具体位置。

  • 校验位数量公式:假设有 n 个信息位,需要添加 k 个校验位。为了让这 k 个校验位能指示出所有 (n + k) 个位置的错误状态以及 1 种正确状态,必须满足不等式:

    2k ≥ n + k + 1

    (例如:课件中 4位信息位 n = 4,代入公式计算得出至少需要 k = 3 个校验位)

2. 海明码的求解步骤(以信息位 1010 为例)

构建海明码主要分为三步:

  • Step 1:确定位置分布
    • 校验位 Pi 必须放在海明位号为 2i − 1 的位置上(即 H1, H2, H4, H8)。
    • 信息位 Di 按照顺序填入剩下的空缺位置(如 H3, H5, H6, H7)。
  • Step 2:确定校验分组
    • 每个校验位负责校验海明位号二进制表示中特定位为 1 的集合。
    • P1 负责校验位号二进制最低位1 的位置(H1, H3, H5, H7)。
    • P2 负责校验位号二进制次低位1 的位置(H2, H3, H6, H7)。
    • P3 负责校验位号二进制第三位1 的位置(H4, H5, H6, H7)。
  • Step 3:计算校验位的值(偶校验)
    • 利用异或运算()求解,确保每组内的 1 的个数为偶数。
    • 计算得出 P1 = 0, P2 = 1, P3 = 0。拼接得到最终的 7位海明码。

3. 海明码的检错与纠错机制

接收方收到数据后,需要计算校验方程(Syndrome) S1, S2, S3

  • 计算方法是将接收到的各分组数据再次进行异或运算。
  • 判定规则
    • 如果 S3S2S1 = 000,说明传输无错误
    • 如果 S3S2S1 ≠ 000,其二进制值直接对应出错的海明位号。(例如:计算出 010,对应的十进制是 2,说明第 H2 位出错了,只需将该位翻转即可纠正)

4. 补充考点:全校验位(解决 1位错与 2位错的区分)

标准的海明码具有“纠 1位错,检 2位错”的能力,但仅凭 S3S2S1 无法区分到底是发生了 1位错还是 2位错。

  • 解决方案:在最高位(如 H8)增加一个“全体偶校验位”(P),对前面的所有位进行整体偶校验。
  • 综合判定逻辑
    1. S3S2S1 = 000 且 整体校验成功 无错误
    2. S3S2S1 ≠ 000 且 整体校验失败 有 1位错(直接用 S 的值定位并纠正)。
    3. S3S2S1 ≠ 000 且 整体校验成功 有 2位错(此时海明码失效,无法纠正,必须要求重传
QQ20260513-100924
QQ20260513-101157

滑动窗口机制

QQ20260513-112016
QQ20260513-112118

停止等待协议

1. 停止-等待协议的核心机制 (图1)

该协议的运行依赖于四大机制:

  • 滑动窗口机制:发送窗口大小 WT = 1,接收窗口大小 WR = 1。即“发送方发一个,等一个;接收方收一个,确认一个”。
  • 确认机制:如果接收方收到 i 号帧且没有检测出差错,必须给发送方返回一个确认帧(ACK_i)。
  • 重传机制(超时重传):发送方发出数据后会启动计时器。如果超时仍未收到对应的 ACK_i,则认为数据丢失,会重新发送 i 号帧。
  • 帧编号:为了区分不同的帧,需要给帧编号。在 S-W 协议中,由于窗口大小都是 1,仅需 1 bit(即 0 和 1 交替使用)即可满足编号要求。

2. 帧的交互与序号概念 (图2)

  • 发送方与接收方交替使用 01 作为帧序号(如 Data0, Data1, Data0…)。
  • 帧结构:数据帧包含首部、可长可短的数据部分和尾部;而确认帧(ACK帧)通常只有首尾的控制信息(如帧序号、帧类型),数据部分非常短甚至为空。
  • 正常交互过程:发送方发送 Data0,接收方收到后回复 ACK0;接着发送方再发送 Data1,以此类推。
QQ20260513-112732

3. 异常处理:超时重传 (图3)

图片演示了网络中出现异常的情况:

  • 如果发送方发送了帧 C(序号为0),但在计时器超时前没有收到对应的确认帧(可能是数据帧在路上丢失,也可能是ACK帧丢失或迟到)。
  • 发送方的时间一到,就会触发“超时重传”机制,重新发送一遍序号为 0 的帧 C。

4. 核心探讨:为什么要给帧编号? (图4)

这是该协议的一个重要考点:

  • 如果没有帧序号会发生什么?

    如果发生超时重传,接收方再次收到同一个帧时,将无法判别这是一个新到达的数据帧,还是一个“重复帧”(即由于之前自己发的ACK丢了,发送方重新发来的老数据)。

  • 为什么 1 bit 就够了?

    因为在 S-W 协议中,接收窗口和发送窗口的距离不超过 1,新帧和旧帧只会在相邻的两次传输中产生混淆,因此用 0 和1 交替表示(1 bit)就已经足够区分了。

后退 N 帧协议(Go-Back-N, 简称 GBN 协议)

1. GBN 协议的核心机制

  • 滑动窗口大小:发送窗口 WT > 1(可以连续发送多个数据帧而无需等待确认),接收窗口 WR = 1(接收方只能按顺序依次接收一个帧)。
  • 累积确认(Cumulative ACK):这是 GBN 的特殊规则。接收方不需要每收到一个帧就发一个确认,它可以连续收到多个数据帧后,只发送最后一个正确按序到达帧的确认帧(ACK_i)ACK_i 的含义是:“序号 i 及其之前的所有帧我都已经正确收到了”。
  • 超时重传(后退 N 帧):如果发送方的计时器超时未收到 ACK_i,发送方不仅要重传第 i 号帧,还必须重传第 i 号帧之后已经发送出的所有帧。这就是“后退 N 帧”名字的由来。
QQ20260514-082558

2. 异常情况示例与处理

  • 数据帧丢失 / 失序到达

    • 假设发送方连续发送了 0、1、2 号帧,但 1 号帧在路上丢失了。
    • 接收方正确收到了 0 号帧(接收窗口向前移动,期待 1 号帧)。接着接收方收到了 2 号帧。
    • 由于 2 号帧超出了当前 WR = 1 的接收窗口(接收方只想要 1 号),2 号帧被视为“非法帧”并被直接丢弃。
    • 接收方会再次向发送方返回 ACK0(目前已按序接收的最后一个正确帧),提醒发送方:“我还在等 1 号帧”。
    • 最终发送方超时,被迫“后退”,重新发送 1 号帧和 2 号帧。
    image-20260514083851115
  • 确认帧(ACK)丢失

    • 如果前面的 ACK 丢失,但后续的 ACK 成功到达,由于“累积确认”机制,发送方不会重传。
    • 如果一段时间内没有任何 ACK 到达导致超时,发送方会重置计时器,并重传该窗口内未被确认的所有数据帧。

3. 窗口尺寸的强制约束

为了让接收方能正确区分新帧和旧帧(避免重复帧被当成新帧接收),帧序号的比特数 n 与窗口大小必须满足严格的数学关系:

  • 公式WT + WR ≤ 2n
  • 反面探讨:如果采用 2bit 编号(序号为 0, 1, 2, 3,即 2n = 4),在 WR = 1 的情况下,发送窗口 WT 最大只能是 3。如果强行设置 WT = 4,当发送方发出 0, 1, 2, 3 号帧且接收方全部收到后,接收方期待下一个新 0 号帧。若此时 ACK 丢失导致发送方超时重传旧 0 号帧,接收方会将其错误地当作新数据接收,导致严重的数据错乱。
QQ20260514-083228

4. GBN 协议的优缺点

  • 优点:相比停止-等待协议,允许连续发送多个帧,提高了传输效率。
  • 缺点:如果信道误码率很高或接收方处理慢,一旦中间某个帧出错,发送方必须把后面哪怕已经正确传达的帧也全部重传一遍,导致传输效率大幅下降
QQ20260514-083247

选择重传协议(Selective Repeat, 简称 SR 协议)

1. SR 协议的核心机制与革新

  • 滑动窗口机制:发送窗口 WT > 1接收窗口 WR > 1。这是与 GBN 最大的区别。接收窗口大于 1 意味着接收方可以缓存失序到达的帧,而不会像 GBN 那样直接丢弃。
  • 确认机制(一帧一确认):SR 协议不再使用累积确认。接收方收到哪个帧,就单独为那个帧发送确认帧(ACK_i)。
  • 否定确认(NAK):如果接收方发现某个帧有差错(如校验和错误),不仅会丢弃它,还会主动向发送方返回一个否定确认帧(NAK_i,要求发送方立即重传该帧,而不需要死等计时器超时。
  • 选择重传:当发送方发生超时或收到 NAK_i 时,仅需重传那一个出错或丢失的 i 号帧,无需重传后续已经发送的帧。

2. 异常情况处理示例

  • 数据帧丢失:发送方发出 0~5 号帧,其中 5 号帧丢失,6、7、0 号帧失序到达接收方。由于接收窗口够大,接收方会缓存 6、7、0 号帧并分别返回 ACK。当发送方的 5 号帧超时后,发送方仅重传 5 号帧
  • 数据帧出错(被丢弃):5 号帧在传输中发生位错,接收方检测出差错后将其丢弃,并主动发送 NAK 5。发送方收到 NAK 5 后,立即请求重传 5 号帧(无需等待超时)。
QQ20260514-085126
  • 确认帧(ACK)丢失:如果某个帧的 ACK 丢失导致发送方超时重传,接收方会再次收到该帧。接收方发现这是一个落在当前接收窗口之外(或者在窗口内但已被标记接收)的重复帧,接收方会直接丢弃该帧,但必须重新返回一次该帧的 ACK,以让发送方能向前滑动窗口。

3. 窗口尺寸的强制约束

为了保证发送方和接收方能正确识别新帧和旧帧(避免重复帧落入新窗口被错误接收),SR 协议对窗口大小有严格的数学要求:

  1. 最大序号限制WT + WR ≤ 2nn 为帧序号的比特数)。
    • 课件探讨:如果用 3bit 编号(0~7),若 WT = 5, WR = 4(不满足公式),当 0~4 号帧的 ACK 全部丢失,发送方超时重传旧的 0 号帧时,接收方窗口已经滑动到了等待新 0 号帧的位置,此时接收方会错误地将“旧 0 号帧”当成“新 0 号帧”接收。
  2. 收发窗口比例:通常要求 WR ≤ WT(接收窗口不能大于发送窗口,因为没必要)。在实际应用中,通常取两者的最大允许值,即 WT = WR = 2n − 1
QQ20260514-085845

协议信道利用率

1. 核心概念与符号定义

在计算信道利用率时,一个完整的“发送-确认”周期(Cycle)包含以下几个时间分量:

  • TD数据帧的传输时延(发送时延)。即把数据比特流推送到链路上所需的时间。
  • RTT往返传播时延。等于 单向传播时延,即信号在物理介质中跑一个来回的时间。
  • TA确认帧(ACK)的传输时延(发送时延)。在很多考题中,由于 ACK 帧很短,这个时间常被忽略(视为 0)。

一个完整的周期时间 = TD + RTT + TA

2. S-W 协议的信道利用率

  • 工作机制:发送方每发送 1 个数据帧,就必须停下来等待确认,收到 ACK 后才能发送下一个帧。

  • 理想利用率公式

    $$U = \frac{T_D}{T_D + RTT + T_A}$$

  • 物理意义:在一个长长的周期时间里,信道只有在 TD 这段时间是在真正传输有效数据的,其余时间都在“空等”,因此信道利用率往往非常低

  • 课件示例$U = \frac{4}{4 + 2 \times 7 + 1} \approx 21\%$

QQ20260514-090835

3. GBN 与 SR 协议的信道利用率(滑动窗口协议)

  • 工作机制:发送方拥有大小为 N 的发送窗口,可以连续不断地向信道中“注入” N 个数据帧,而无需等待每个帧的 ACK。

  • 理想利用率公式

    $$U = \frac{N \cdot T_D}{T_D + RTT + T_A}$$

  • 物理意义:由于连续发送了 N 个帧,在一个周期内的有效数据发送时间变长了(N ⋅ TD),因此信道利用率得到了大幅提升。注意:信道利用率的极限是 1(即 100%),不能超过这个值。

  • 课件示例N = 4 时,$U = \frac{4 \times 4}{4 + 2 \times 7 + 1} \approx 84\%$

QQ20260514-091433

4. 易考点与术语补充

  • 窗口大小对利用率的影响:对于采用 n 比特进行帧编号的系统,必须满足 WT + WR ≤ 2n
    • GBN 协议的接收窗口 WR = 1
    • SR 协议的接收窗口 WR > 1
    • 因此,在相同的比特位 n 下,GBN 协议可以配置出更大的发送窗口 WT,在理论上其所能达到的最大信道利用率会比 SR 协议更高。
  • 术语辨析
    • 滑动窗口协议:特指 GBN 或 SR 协议。
    • 连续 ARQ 协议:也是指 GBN 或 SR 协议。
    • ARQ 协议:是 S-W、GBN、SR 协议的总称。
QQ20260514-092437

介质访问控制(MAC)

1. 介质访问控制(MAC)的概念

  • 问题背景:当多个节点(如A, B, C, D, E)共享同一个“总线型”广播信道时(例如早期的同轴电缆局域网,或现在的 WiFi、5G 等无线通信),如果多个节点同时发送数据,就会发生“信号冲突”。
  • 核心目的:介质访问控制(MAC)就是一套规则,用来控制各个节点对传输介质的访问权限,从而减少甚至避免冲突

2. 频分复用(FDM, Frequency Division Multiplexing)

  • 原理:将共享信道的总频带(总带宽,Hz)在频率维度上划分成多个独立的“子频带”。每个子频带作为一个子信道,分配给一对用户专属使用。
  • 技术细节:发送端通过“复用器”将各个节点发出的信号复合到共享信道上;接收端通过“分用器”将不同子频带的信号分离开来。为了防止子信道之间相互干扰,子频带之间必须留有“隔离频带”。
  • 优缺点
    • 优点:各节点可以同时发送信号,互不干扰,充分利用了信道带宽。
    • 缺点:主要用于模拟信号的传输。
QQ20260514-102253

3. 波分复用(WDM, Wavelength Division Multiplexing)

  • 原理:本质上就是光的频分复用。根据物理公式 C = λf(光速 = 波长 × 频率),光的频率与波长成负相关。波分复用就是将不同波长的光信号复合在一起,在同一根光纤中传输。
  • 特点:由于光信号的频带范围(带宽)极其庞大,一根光纤在逻辑上可以被拆分成海量的子信道,极大地提升了传输容量。

4. 时分复用(TDM, Time Division Multiplexing)

  • 原理:将共享信道的传输时间划分为等长的“TDM帧”,每个TDM帧内部再划分为等长的 m 个“时隙”(Time Slot)。将这 m 个时隙固定分配给 m 对用户使用。
  • 特点:用户在分配给自己的时隙内,独占整个信道的频率带宽;但在时间上,各用户是轮流使用信道的。
  • 致命缺点:分配是静态的、固定的。如果某个节点暂不发送数据,分配给它的“时隙”就会一直空着(闲置),导致信道利用率很低。并且每个节点最多只能分到信道总带宽的 1/m

5. 统计时分复用(STDM, Statistic Time Division Multiplexing)

为了解决 TDM 效率低下的问题,引入了 STDM(也称异步时分复用)。

  • 原理:在 TDM 的基础上,不再固定分配时隙,而是根据统计到的各个节点对信道的使用需求,动态按需分配时隙
  • 优点
    • 如果某个节点不发数据,就不会分给它时隙,从而避免了时隙闲置,信道利用率更高
    • 在极端情况下(比如只有节点 A 需要大量发数据),节点 A 可以在一段时间内连续占用所有的时隙,从而瞬间获得所有的信道带宽资源。
QQ20260514-100531

码分复用(Code Division Multiplexing, 简称 CDM)

1. 码分复用(CDM)的核心设计思想

与时分(分时间)、频分(分频率)不同,码分复用允许多个节点在同一时间使用同一频带进行通信,它是通过一种数学编码手段来区分不同信号的。

  • 专属码片序列:网络中的每个节点都会被分配一个独一无二的“码片序列”,通常包含 m 个码片。这可以看作是一个 m 维向量(分量通常取 1−1)。
  • 正交要求(核心前提):为了让信号混合后还能被成功分离,分配给各节点的 m 维向量必须相互正交。即任意两个不同节点的向量进行内积计算,结果必须为 0(如 a⃗ ⋅ b⃗ = 0)。

2. 发送方如何发送数据?

当节点需要发送二进制比特流时,遵循以下规则:

  • 发送比特 1:节点发出与自身专属“码片序列”完全相同m 个信号值(即原向量 a⃗)。
  • 发送比特 0:节点发出与自身专属“码片序列”完全相反m 个信号值(即取反向量 a⃗)。

3. 信号的传输与“叠加”

  • 当多个节点(如节点 A 和节点 B)同时向信道发送数据时,它们发出的电磁波信号会在物理信道中产生叠加。
  • 在数学上,这种物理叠加本质上就是多个 m 维向量的线性加法(如 a⃗ + b⃗a⃗ + (−b⃗))。

4. 接收方如何“分离”并接收数据?

接收方收到的是混合后的“叠加信号”。由于接收方知道网络中所有节点的专属码片序列,它可以利用正交特性进行数学分离:

  • 规格化内积:将收到的“叠加信号”与目标发送方的“码片序列”进行规格化内积运算(即向量点乘后除以维数 m)。
  • 数学消除:由于不同节点的向量相互正交,在计算内积时,其他节点的干扰信号会被消去(乘积为 0),只留下目标节点的信号。
  • 解码判定结果
    • 如果规格化内积结果为 1,表示该发送方发送了比特 1
    • 如果规格化内积结果为 −1,表示该发送方发送了比特 0
    • 如果结果为 0,表示该发送方在此刻没有发送数据
QQ20260514-104017

随机访问介质访问控制

image-20260518164351616

上半层:LLC(逻辑链路控制)—— 转运中心的“调度文员”

  • 位置: 紧贴着网络层(第 3 层)。
  • 核心职责:
    • 识别与复用(Multiplexing): 拆开从下层送上来的包裹,看看里面的数据到底是要交给 IP 协议、ARP 协议,还是其他网络层协议。(这就是它“逻辑”的体现,它负责软件层面的对接)。
    • 提供服务接口: 根据需求提供无确认的无连接服务、面向连接的服务等(虽然在现代以太网中,这部分功能被弱化了)。
  • 特点: 它完全是个纯软件逻辑概念,与具体的物理传输介质(光纤、铜线、空气)毫无关系。

下半层:MAC(介质访问控制)—— 转运中心的“车队队长”

  • 位置: 紧贴着物理层(第 1 层)。
  • 核心职责:
    • 组装包裹(成帧): 给 LLC 交下来的数据加上“源 MAC 地址”和“目的 MAC 地址”(我们在局域网里认的就是这个硬件地址),并在末尾加上我们前面算过的 CRC 校验码(FCS)
    • 交通指挥(介质访问): 决定这一秒钟,网卡到底能不能往物理线路上发脉冲信号(也就是我们前面讨论的 CSMA 机制)。
  • 特点: 它和物理硬件深度绑定。你换一块 Wi-Fi 网卡和插一根网线,用的就是完全不同的 MAC 协议。
QQ20260514-175134

1. ALOHA 协议家族

这是最基础的随机访问协议,核心特点是“想发就发”,缺乏对信道状态的探测。

  • 纯 ALOHA (Pure ALOHA)
    • 规则:节点只要准备好数据帧,就立刻发送到信道上。
    • 冲突处理:如果发送后超时未收到确认(ACK),说明发生了冲突。节点会随机等待一段时间后再次尝试重传。
    • 缺点:由于完全不关心信道是否被占用,多节点发送时极易发生帧在时间上的重叠(冲突),信道利用率极低。
  • 时隙 ALOHA (Slotted ALOHA)
    • 规则:将时间划分为等长的“时隙”(大小等于传输一个最大帧的时间)。节点只能在每个时隙的开始时刻才能发送数据。
    • 优点:通过强制时间同步,避免了用户发送数据的随意性。帧要么完全不冲突,要么完全重叠冲突,消除了一半的冲突情况,相比纯 ALOHA 降低了冲突概率,提高了信道利用率。
image-20260514112359990
QQ20260514-105359

2. CSMA 协议(载波监听多路访问)

CSMA 的英文全称是 Carrier Sense Multiple Access,中文通常翻译为载波监听多路访问

为了弥补 ALOHA 协议“不听就发”的缺陷,引入了 CSMA 协议。

  • 核心改进(先听后发):在发送数据之前,必须先监听信道是否空闲。只有在信道空闲时,才会尝试发送。

根据监听策略和发送时机的不同,CSMA 衍生出三种不同的具体协议:

① 1-坚持 CSMA (1-Persistent CSMA)

  • 规则
    • 如果信道空闲,立刻发送数据(发送概率为 1)。
    • 如果信道忙碌,节点会“坚持”监听信道,直到信道变得空闲,然后立刻发送。
  • 优缺点:信道利用率高(一旦空闲马上被利用)。但冲突概率大:如果多个节点都在等待信道空闲,一旦信道释放,它们会同时发送数据,必然导致冲突。
image-20260514112458260

② 非坚持 CSMA (Non-Persistent CSMA)

  • 规则
    • 如果信道空闲,立刻发送数据。
    • 如果信道忙碌,节点放弃监听信道,随机推迟一段时间后,再重新尝试监听。
  • 优缺点:节点通过随机推迟“错开”了发送时间,大大降低了冲突概率。但由于节点盲目等待,可能信道已经空闲了节点还在等待,导致信道利用率降低
image-20260514112514738

③ p-坚持 CSMA (p-Persistent CSMA)

  • 规则
    • 如果信道忙碌,节点“坚持”监听。
    • 如果信道空闲,节点以概率 p 立刻发送数据,以概率 1-p 推迟一段特定的时间(通常是一个端到端传播时延)再重新监听。
  • 优缺点:这是 1-坚持和非坚持方案的折中。既保留了“坚持监听”以提高利用率的优点,又通过“概率发送”降低了多个节点同时发送导致的冲突概率。
image-20260514112524276
QQ20260514-110951

CSMA/CD 协议(带冲突检测的载波监听多路访问)

QQ20260514-171125

1. CSMA/CD 协议的核心口诀

  • 先听后发:发送数据前先监听信道是否空闲(基于 1-坚持 CSMA,即若忙碌则坚持监听,一旦空闲立即发送)。
  • 边听边发:在发送数据的同时,持续监听信道,检测发出的信号是否与其他节点的信号发生碰撞。
  • 冲突停发:一旦检测到冲突,立即停止发送,以节省信道资源。
  • 随机重发:发生冲突后,根据特定的算法随机等待一段时间再重新尝试发送。
QQ20260514-171758

2. “争用期”与无冲突确认

  • 定义:争用期是指一个节点发出数据后,最多需要多久才能确信自己成功“占领地盘”(即确认没有发生冲突)。
  • 计算公式争用期 = 2 × 最大单向传播时延。这考虑了最极端的情况:两端距离最远的节点 A 和 B,A 发出的信号刚要到达 B 时,B 也开始发送,冲突信号传回 A 刚好需要两倍的传播时延。
  • 重要结论:CSMA/CD 协议没有 ACK 机制。如果节点在经过一个“争用期”后依然没有检测到冲突,就可以断定这次发送一定成功,后续就不可能再发生冲突了。
QQ20260514-173505

3. “最短帧长”的限制

这是为了保证“边听边发”机制有效而设立的硬性规定。

  • 原因:节点只有在发送数据的过程中才能检测冲突。如果帧太短,节点很快就发完了,但此时冲突信号还在路上(还没传回节点)。当冲突信号到达时,节点已经发完了,它会“误以为”发送成功,从而导致错误。

  • 计算公式

    最短帧长 = 争用期 × 信道带宽 = 2 × 最大单向传播时延 × 信道带宽

    (注:以太网规定最短帧长为 64B 即 512bit)

  • 处理规则:如果实际要发的数据很少,必须“填充”无用数据至合法长度后再发送。接收方若收到小于最短帧长的数据,会直接视为因冲突而中断的无效帧(非法帧)。

QQ20260514-174654

4. 冲突后的退避:截断二进制指数退避算法

当检测到冲突后,节点需要决定等待多久再重发,这里采用的是动态调整等待范围的算法:

  • 退避时间 = r × 争用期,其中 r 是从指定区间内随机抽取的一个整数。
  • 区间计算逻辑:设冲突次数为 k
    1. k ≤ 10 时,从 [0, 2k − 1] 区间内随机取整数 r。随着冲突次数增加,区间成指数级扩大,意味着节点可能等待的时间越来越长,以此来疏散拥挤的信道。
    2. 10 < k ≤ 15 时,区间不再扩大,固定为 [0, 210 − 1](即 [0, 1023])。
    3. 分水岭:当第 16次冲突发生时,协议会直接“躺平”,放弃传输该帧,并向上级网络层报告错误。

CSMA/CA 协议(带冲突避免的载波监听多路访问)

1. 为什么 WiFi 不用 CSMA/CD?

课件首先对比了有线网的 CSMA/CD 和无线网的 CSMA/CA,解释了无线网络不能使用“冲突检测(CD)”的两个致命原因:

  • 硬件实现极其困难:在无线环境中,设备自身发出的信号强度往往远大于接收到的微弱信号(因为信号在空气中衰减极快)。“边发边听”会导致自己的声音掩盖了别人的声音,根本无法有效检测出冲突。
  • “隐藏站”问题:以太网的碰撞检测是假定了所有的站点都能够听到其他站点是否在发送数据。但在无线局域网的工作环境中,这个假定是不能成立的。在无线通信中,并非所有节点都能互相听见。比如节点 A 和 C 都离中心热点(AP)很近,但 A 和 C 彼此距离很远(超出了信号范围)。此时 A 监听信道会觉得“空闲”,C 也觉得“空闲”,如果它们同时向 AP 发送数据,就会在 AP 处发生严重的冲突。对 A 而言,C 就是一个看不见的“隐藏站”。
QQ20260514-180412

2. CSMA/CA 的核心要点(先发制人,尽量避免)

既然无法在发送中检测冲突,CSMA/CA 的思路就是在发送前想尽一切办法避免冲突

  • 先听后发
    • 若信道空闲,必须再等待一个DIFS(分布式协调功能帧间间隔)的时间后,才能发送帧。一旦开始发送,就一口气发完,中途不检测冲突。
    • 若信道忙碌,则强制进入“随机退避”状态。
  • “随机退避”原理
    • 使用二进制指数退避算法设定一个随机倒计时。
    • 冻结机制:节点在倒计时期间必须保持监听。只有当信道“听起来”空闲时,倒计时才扣减;一旦信道忙碌,倒计时立即冻结。当倒计时归零时,信道必然是空闲的,节点立即发送数据。
  • 确认机制(ACK):因为无法检测冲突,发送方必须依靠接收方返回的 ACK 帧 来确认是否发送成功。如果超时未收到 ACK,发送方只能认为发生了冲突,再次进入“随机退避”并重传(类似于停止-等待协议)。

3. 信道预约机制(解决“隐藏站”的杀手锏)

为了彻底解决隐藏站问题,CSMA/CA 提供了一个可选的信道预约功能(RTS/CTS):

  1. RTS(请求发送):发送方(如节点 A)在发数据前,先向 AP 广播一个极短的控制帧 RTS,里面包含了预计要占用信道的时间。
  2. CTS(允许发送):AP 收到 RTS 后,向四周广播一个 CTS 帧作为回应,里面同样包含预计占用时间。
  3. 虚拟载波监听(禁言机制):关键在于这一步。周围所有的节点(包括 A 的隐藏站 C)一旦收到了 AP 发出的 CTS 帧,就会自动读取里面的时长信息,并设置一个内部计时器(NAV),在此期间自觉保持“禁言”,绝对不发送任何数据。
  4. 安全发送:节点 A 收到 CTS 后,就可以安心地发送冗长的数据帧了,因为此时整个网络都已被 AP “清场”。

4. 帧间间隔(IFS, InterFrame Space)

为了区分不同类型帧的优先级,协议规定了三种长度的等待时间:

  • SIFS(最短):留给接收方处理数据并立即回复(如发送 ACK 或 CTS)的时间,优先级最高。
  • PIFS(中等):用于 PCF 协调功能(考研通常不要求深入)。
  • DIFS(最长):普通的“帧事务”开始前必须等待的时间,优先级最低,为了让高优先级的控制帧先行。
QQ20260514-175254
QQ20260515-095412
QQ20260515-095750

局域网的基本概念和体系结构

QQ20260515-085544

1. 局域网(LAN)的分类与特性

  • 三大特性:覆盖较小的地理范围;具有较低的时延和误码率;局域网内的各节点之间以“帧”为单位进行传输。
  • 传输方式:支持单播(1对1,如A发给B)、广播(1对全体,如A发给所有人)、多播/组播(1对部分特定节点,如A发给B,D,E)。
  • 两大分类
    • 有线局域网:如以太网(IEEE 802.3)、早期已被淘汰的令牌环网。
    • 无线局域网:如 WiFi(IEEE 802.11),采用星形拓扑(1个AP+多台移动设备),使用 CSMA/CA 协议。

2. 以太网(Ethernet)的发展演进

课件详细梳理了以太网传输介质和物理拓扑的演进路线:

  • 早期同轴电缆以太网(10Base5):物理上和逻辑上都是总线形,采用 CSMA/CD 协议争抢信道。
  • 双绞线以太网(10BaseT等)
    • 使用集线器(Hub):物理上是星形,但逻辑上依然是总线形,工作在半双工模式,必须使用 CSMA/CD 协议。
    • 使用交换机(Switch):物理和逻辑上都是星形。交换机支持全双工通信,此时各节点不再需要争抢信道,不需要使用 CSMA/CD 协议(这是一个重要考点)。
  • 光纤以太网(10BaseF等):主要用于扩大局域网覆盖范围。如果是点对点连接且用两条光纤实现全双工通信,同样不需要使用 CSMA/CD。

3. 网络适配器(网卡 / NIC)的核心工作原理

网卡是连接计算机主机与外部局域网的桥梁(硬件架构包含以太网适配器和WiFi适配器)。

  • MAC 地址(物理地址):全球唯一,长 48 bit
    • 高 24 bit 由 IEEE 统一分配给不同的网卡制造厂商。
    • 低 24 bit 由厂商自行分配给生产出的每一块网卡。
    • MAC 地址固化在网卡的 ROM(只读存储器)中。
  • 网卡的五大核心要点(功能)
    1. 封装与解封装:负责把网络层交下来的 IP 数据报封装成“帧”发送到局域网;或从局域网接收“帧”并提取出 IP 数据报。
    2. 中断机制与差错处理:从局域网接收信号,如果收到正确的帧,就使用“中断”通知 CPU 提取数据;如果发现是差错帧,网卡会直接将其静默丢弃,不打扰 CPU。
    3. 协议实现:网卡内部依靠硬件实现了数据链路层物理层的标准功能。
    4. 串 / 并行转换:网卡与主板(CPU/内存)之间通过 I/O 总线进行并行通信,而与外部局域网之间进行串行通信,网卡负责这两种数据流格式的转换。
    5. 帧缓冲:网卡内部带有 RAM(随机存取存储器),用于在发送或接收数据时对“帧”进行缓存。

以太网(Ethernet)的物理层标准、MAC 帧格式

1. 以太网标准与双工模式

不同传输介质对通信模式(半双工/全双工)的支持情况不同:

  • 同轴电缆(如 10BASE5、10BASE2):物理特性决定了它们只能工作在半双工模式。
  • 双绞线(如 10BASE-T):
    • 做题时的默认潜规则:如果连接的是集线器(Hub),只能是半双工(必须使用 CSMA/CD 协议防冲突)。
    • 如果连接的是交换机(Switch),默认支持全双工(各走各的道,不需要使用 CSMA/CD 协议)。
  • 光纤(如 10BASE-F):只支持全双工
  • 高速以太网演进:当以太网速率达到 10Gbps(万兆)时,规定只工作在全双工模式,这也标志着 CSMA/CD 协议在现代高速网络中已经彻底退出历史舞台。
QQ20260515-085819

2. V2 版以太网 MAC 帧格式(重点记忆)

课件给出了一个非常实用的结构记忆口诀:“6 6 2 N 4,收发协议数验”

  • 目的地址(6字节):接收方的 MAC 地址。如果是广播帧,该地址的 48 bit 全为 1(即 FF-FF-FF-FF-FF-FF)。
  • 源地址(6字节):发送方的 MAC 地址。
  • 类型(2字节):指明封装在内部的网络层使用的是什么协议(如 IPv4 或 IPv6)。
  • 数据(N = 46~1500字节):即网络层交下来的 IP 数据报。如果太短必须“填充”凑够 46 字节,太长则在网络层就要“分片”。
  • FCS 校验码(4字节):使用之前学过的 CRC(循环冗余校验)来检测位错。
  • 易错细节:为了让接收方对齐时钟节奏,物理层会在这个 MAC 帧前面强行插入 8 字节的“前导码”(7B 前同步码 + 1B 帧开始定界符)。但这 8 字节不算作 MAC 帧本身的长度。MAC 帧的总长度被严格限制在 64B ~ 1518B 之间。
QQ20260515-090348

3. MAC 地址的归属与设备分级

  • 核心理念:MAC 地址是数据链路层(L2)的概念。
  • 结论:工作在网络层的路由器、工作在链路层的交换机内部都有数据链路层的功能,因此它们都有 MAC 地址。而工作在物理层(L1)的集线器(Hub)*只负责无脑放大电信号,它*没有 MAC 地址

4. 冲突域与广播域的隔离(必考核心)

这是构建和分析网络拓扑时最关键的原则,决定了广播风暴和冲突的范围:

  • 集线器(Hub):最底层的物理层设备。不隔离冲突域,也不隔离广播域。连在上面的所有电脑“一荣俱荣,一损俱损”,只要有两人同时发数据就冲突,一人发广播所有人必听。
  • 交换机(Switch):数据链路层设备。隔离冲突域,但不隔离广播域。它的每个端口就是一个独立的冲突域(所以连在不同端口的设备可以同时发数据不冲突),但如果有人发广播帧,交换机依然会复制并群发给所有端口。
  • 路由器(Router):网络层设备。既隔离冲突域,也隔离广播域。路由器是广播帧的“终结者”。当路由器收到目的地址全为 1 的广播 MAC 帧时,它会直接拦下丢弃,绝不会将其转发到其他网络段。
QQ20260515-091828

VLAN(虚拟局域网)

1. VLAN 的引入与核心概念

  • 面临的问题:如果一个局域网非常大(连接了大量主机和交换机),整个网络就是一个巨大的“广播域”。一旦有设备发送广播帧,所有设备都会收到,容易引发“广播风暴”,浪费网络资源,且不利于信息安全。
  • VLAN 的作用:在逻辑上将一个大型的物理局域网,分割成若干个较小的虚拟局域网(VLAN)。
  • 核心准则:一个 VLAN 就是一个独立的广播域。 同一个 VLAN 内的主机可以直接通信,不同 VLAN 之间的主机即使连在同一个交换机上,也不能直接进行二层(MAC层)通信,从而隔离了广播。
  • 标识:每个 VLAN 都有一个专属的编号,称为 VID (VLAN ID)

2. 划分 VLAN 的三种方式

交换机内部会维护一张映射表,将数据帧归入不同的 VLAN 中:

  • ① 基于接口:最简单常用的方式。交换机直接记录“端口号”与“VID”的映射关系(例如:插在 1、2 号口的电脑属于 VLAN 10)。
  • ② 基于 MAC 地址:记录“主机的 MAC 地址”与“VID”的映射关系。优点是即使用户把电脑拔下来换插到交换机的另一个端口,它依然属于原来的 VLAN。
  • ③ 基于 IP 地址:记录“网络层 IP 地址”与“VID”的映射关系。这种方式甚至允许 VLAN 的范围跨越路由器。

3. IEEE 802.1Q 帧(重点考点)

为了让跨越多个交换机的同一 VLAN 主机能够通信,交换机之间通过“干线链路(Trunk 链路)”相连。干线链路需要传输多个不同 VLAN 的数据,因此必须对数据帧进行“打标签”以示区分。

  • 传输规则
    • 主机与交换机之间:传输的是无标签的标准以太网 MAC 帧(即之前背的口诀:6 6 2 N 4)。
    • 交换机与交换机之间(干线链路):传输的是打了标签的 802.1Q 帧。交换机在发往另一台交换机前会“插入”标签,另一台交换机收到后,在发给目标主机前会“剥除”标签。
  • 802.1Q 帧的结构演变
    • 在标准 MAC 帧的“源地址(6B)”“类型(2B)”之间,强行插入了 4 字节的 VLAN 标签
    • 新口诀6 6 4 2 N 4,收、发、V(VLAN标签)、协、数、验。
  • 4 字节 VLAN 标签的内部构造
    • 前 2 个字节:固定为 0x8100,交换机一看到这个数字就知道这是一个打了标签的 802.1Q 帧。
    • 后 2 个字节:包含 4 位的无用信息和 12 位的 VID。这 12 位的 VID 明确指出了该帧属于哪一个具体的 VLAN。
QQ20260515-102011
QQ20260515-102025

IEEE 802.11 无线局域网

一、 802.11 无线局域网基本概念

802.11 无线局域网采用星形拓扑结构,其中心节点称为接入点(AP),也可称为无线接入点(WAP)。

  • 基本服务集 (BSS): 由 1 个基站(AP)和 N 个移动站组成。
    • 通俗理解:1 个 WiFi 热点连了很多台手机或电脑。
    • SSID (服务集标识符): 无线局域网的名字(WiFi名称),最长不超过 32 字节。
    • BSA (基本服务区): 一个基本服务集能够覆盖的地理范围(即站在哪里能搜到这个 WiFi)。
  • 扩展服务集 (ESS): 将多个 AP 连接到同一个分配系统(DS),组成一个更大的服务集。
    • 通俗理解:想象成家里的“全屋 WiFi”系统。
  • 漫游 (Roaming): 移动站从一个基本服务集切换到另一个基本服务集,且保持通信连续不中断。
    • 通俗理解:拿着手机走动时,丝滑切换不同的 WiFi 热点。
  • 门户 (Portal): 充当“网桥”的角色,可将 802.11 无线局域网接入 802.3 有线以太网,将两类局域网连接成更大的局域网。
QQ20260515-103344

二、 硬件架构与通信原理

  • 家用路由器架构: 一个普通的家用路由器实际上是三者的结合体:家用路由器 = 路由器 + 以太网交换机 + AP
  • 通信限制: 在 802.11 无线局域网内,两个移动站之间不能直接通信,必须通过基站(AP)进行转发。
  • 链路区别: AP 与移动站之间通过无线链路传输;AP 与 AP、AP 与路由器、AP 与交换机之间通常使用有线链路
  • 格式转换: AP 通常具备“帧格式转换”功能,能在无线链路的 802.11 帧与有线链路的以太网帧之间互相转换。
  • 介质访问控制: 802.11 无线局域网使用 CSMA/CA 协议来实现介质访问控制(包含 RTS 和 CTS 帧的碰撞避免机制)。

三、 802.11 帧的分类

802.11 帧主要分为以下三种类型:

  1. 数据帧: 承载实际传输的数据。
  2. 控制帧: 用于辅助数据传输,如 ACK(确认)、RTS(请求发送)、CTS(允许发送)。
  3. 管理帧: 用于管理无线网络连接,如探测请求/探测响应帧。
    • 通俗理解:主要用于“发现 WiFi”。

四、 802.11 数据帧格式与地址解析

这部分是重难点,尤其涉及 MAC 首部中的地址字段(重点关注地址 1、2、3)以及帧控制位中的“去往 AP”和“来自 AP”的标志位。

核心记忆口诀:

  • 10 表示“去往 AP”(移动站发往 AP):
    • 口诀:去往 AP 中起止
    • 地址 1:转(接收方,即 AP 的 MAC 地址)
    • 地址 2:点(发送方,即源移动站 A 的 MAC 地址)
    • 地址 3:点(最终目的地,即目标移动站 B 的 MAC 地址)
  • 01 表示“来自 AP”(AP 发往移动站):
    • 口诀:来自 AP 止中起
    • 地址 1:点(接收方,即目标移动站 B 的 MAC 地址)
    • 地址 2:转(发送方,即 AP 的 MAC 地址)
    • 地址 3:点(最初源头,即源移动站 A 的 MAC 地址)

(注:地址 4 仅在固定基站的网络互联中使用,一般情况下较少关注。)

QQ20260515-104739
QQ20260515-104952

广域网与ppp协议

一、 广域网 (WAN) 基本概念

  • 特点与任务: 覆盖范围广(几十到几千公里),主要任务是长距离运送主机发送的数据,并将分布在各地的局域网互联起来。
  • 硬件构成: 局域网通过路由器连接到广域网。广域网内部由节点交换机(可理解为广域网专用的专业路由器)通过链路连接而成。
  • 核心对比(重要考点):
    • 广域网 (WAN): 各节点之间通常使用点对点高速链路,数据链路层常使用 PPP 协议
    • 局域网 (LAN): 传统以太网常使用总线型链路,数据链路层常使用 CSMA/CD 协议

二、 PPP 协议核心拆解

PPP(点对点协议)是广域网链路层最常用的协议,主要包含三大组成部分及其相关机制:

1. 三个组成部分

  • LCP (链路控制协议): 用于建立、配置和测试数据链路连接(例如:协商 MTU 大小、身份认证协议)。
  • NCP (网络控制协议): 为网络层协议建立和配置逻辑连接。每个不同的网络层协议需要相应的 NCP(例如:为 IP 协议分配 IP 地址的叫 IPCP)。
  • PPP 帧格式: 定义了将网络层数据(如 IP 数据报)封装成帧的格式。

2. PPP 帧格式详解

  • 标志字段 (F): 首尾各有一个,固定为 0x7E (01111110),作为帧定界符。
  • 地址 (A) 与控制 (C): 固定数值,无实际意义。
  • 协议: 2字节,用于指明“信息部分”携带的是什么协议的数据(如:IP 数据报、LCP 数据或 NCP 数据)。
  • 信息部分: 实际携带的有效载荷,不超过 1500 字节。
  • FCS (帧检验序列): 2字节,采用 CRC 循环冗余校验,若出错则直接丢弃该帧。

3. 透明传输机制

为了防止信息部分出现与帧定界符 0x7E 相同的比特组合导致误判,PPP 采用以下两种填充方式:

  • 字符填充(用于异步传输): 逐个字符传送。以 0x7D 作为转义字符来替换数据中出现的 0x7E 等特殊控制字符。
  • 零比特填充(用于同步传输): 连续比特流传送。发送端在发现连续 5 个 1 时,自动填入一个 0(接收端反向删除),确保数据中不会出现 01111110

4. PPP 协议的主要特点

  • 仅支持点对点全双工链路。
  • 不可靠: 只保证无差错(通过 CRC 检错丢弃),但不提供确认(没有 ACK 机制)。
  • 支持多种网络层协议(如 IPv4、IPv6)。
  • 面向字节,帧长度是字节的整数倍。
QQ20260515-111957

三、 PPP 协议工作过程(以早期拨号上网为例)

当你使用老电脑连接 ISP(网络服务提供商)的接入服务器时,PPP 的工作状态流转如下:

  1. 物理链路建立: 硬件设备连接,建立底层的物理层连接。
  2. LCP 链路建立: 双方通过发送 LCP 请求/应答帧,协商网络参数(如 MTU、认证方式)。
  3. LCP 链路鉴别: 用户发送账号密码进行身份验证(认证成功或失败)。
  4. NCP 链路建立: 鉴别通过后,发送 NCP 请求,ISP 服务器为你分配一个 IP 地址。
  5. 网络连通: LCP 和 NCP 链路均建立完毕,开始使用 PPP 帧携带 IP 数据报愉快地上网。

补充延伸: 随着技术发展,“拨号上网”时代过去后,为了在现代以太网中使用 PPP 协议的认证等优秀特性,演变出了 PPPoE 协议(PPP over Ethernet),其本质就是在以太网 MAC 帧的数据部分套娃携带了 PPP 帧

以太网交换机

QQ20260516-100605

一、 以太网交换机的基本特点

  • 本质: 交换机相当于一个多端口网桥(支持全双工通信)。
  • 工作层次: 工作在数据链路层
  • 转发依据: 交换机根据数据帧的目的 MAC 地址来决定如何转发数据帧。

二、 核心机制:自学习功能(支持即插即用)

交换机内部维护着一张交换表,记录了【MAC 地址】与【端口号】的对应关系。该表初始为空,通过“自学习”不断完善:

  1. 学习(记录“从哪来”):
    • 每当交换机收到一个帧,就会读取该帧的源 MAC 地址(发送方),并将其与接收该帧的端口号记录/更新到交换表中。
  2. 转发(决定“往哪去”):
    • 情况 A(未知目标): 如果交换表中找不到“目的 MAC 地址”(接收方)对应的端口,交换机会将该帧广播到除接收端口之外的所有其他端口。
    • 情况 B(已知目标): 如果交换表中存在“目的 MAC 地址”的对应端口,交换机会精准地将帧转发到该端口(单播)。
  3. 老化机制(应对网络拓扑变化):
    • 思考题指出:如果节点物理位置发生移动(例如拔掉网线换到另一个端口),旧的记录会导致转发错误。
    • 解决方案: 交换表中的每个表项都设有“有效时间”。如果一段时间内没有收到来自该 MAC 地址的帧,该表项就会过期自动作废,从而保证交换机能学习到节点最新的位置。
QQ20260516-100427

三、 两种数据交换方式对比

在决定从哪个端口转发之前,交换机处理数据帧的具体方式分为两种(结合 V2 标准以太网 MAC 帧格式:6 6 2 N 4,即目的地址、源地址、类型、数据、FCS校验):

  • 1. 直通交换 (Cut-through)
    • 工作原理: 交换机接收到帧时,只读取前 6 个字节(目的 MAC 地址),一旦查表确定了输出端口,就立刻开始转发。
    • 优点: 转发时延非常低。
    • 缺点: 无法进行差错检测,也不适用于需要速率匹配或协议转换的线路。
  • 2. 存储转发交换 (Store-and-forward)
    • 工作原理: 交换机会先将整个数据帧完整地接收并放入内部的高速缓存中。接着进行 FCS 差错检测等必要处理。如果帧正确,再根据交换表将其转发出去;如果出错,则直接丢弃。
    • 优点: 能够检测出错误帧并丢弃,同时适用于需要速率匹配、协议转换或差错检测的线路。
    • 缺点: 转发时延较高。
QQ20260516-100952

网络层

网络层的功能

一、 IP地址基础概念

  • 表示方法:IP地址用32位(32bit)二进制数表示。
  • 有点分十进制记法:常以8位(8bit)为一组进行划分,每组转换并记录为十进制数。
    • 具体示例:二进制形式 00111011 10101111 01100001 10011001 对应转换后的点分十进制简记为 59.175.49.153
  • 数值合法范围:由于每组由8位二进制组成,显然每个部分的合法范围都是 0~255

二、 网络层的功能

1. 异构网络互联

  • 如何理解“异构”:指每个网络的拓扑结构不同、物理层 & 链路层的实现不同、主机类型也各不相同。
  • 重要互联设备:路由器(Router)。
  • 别称注脚:在TCP/IP文献中,路由器也常被称为网关(Gateway)。

2. 路由与转发

  • 路由(Routing)
    • 各个路由器之间相互配合,规划IP数据报(分组)的最佳转发路径。
    • 各个路由器需要运行“路由协议”,最终生成各自的“路由表”。
  • 转发(Forwarding)
    • 一台路由器根据自己的“转发表”,将收到的IP数据报从合适的接口转发出去。
    • 精简关系:转发表 = 精简版路由表。更精简的数据结构有助于实现快速检索。

3. 拥塞控制

  • 产生原因:网络上出现过量分组,导致超负荷,进而引起网络性能下降。
  • 表现现象:网络上的分组数增加,但吞吐量反而降低。
  • 形象类比:节假日路上的车辆增多到一定程度时,收费站的吞吐量反而降低。
  • 拥塞控制方法
    • 开环控制(静态的方法):在部署网络时,就提前设计好预防拥塞的方法。一旦网络系统开始运行,就不再修改。
    • 闭环控制(动态的方法)
      • 动态监视网络状态,及时发现哪里发生拥塞,并将拥塞信息传递给相关路由器(如:通过ICMP协议)。
      • 相关路由器在接收到信息后,会及时调整其“路由表”。
2

ipv4分组

image-20260608120731396

一、 各种协议之间的服务关系

整个网络体系结构中,上层协议依赖下层协议提供的服务:

  • 应用层 传输层
    • 基于TCP协议的有:SMTP、POP3(电子邮件)、HTTP(万维网)、FTP(文件传输)。
    • 基于UDP协议的有:DNS(域名系统)、DHCP(动态主机配置)。
  • 传输层 网络层:TCP和UDP协议的数据段最终都要交由 IP协议 封装成 IP分组(数据报)。IP协议是互联网的核心。
  • 网络层辅助协议
    • ARP协议:用于查询同一网络中的 <主机IP地址, MAC地址> 之间的映射关系。
    • ICMP协议:用于在网络层实体之间相互通知“异常事件”。
    • IGMP协议:用于实现IP组播。
  • 网络层 数据链路层 & 物理层:IP分组下发后,由具体的物理网络协议承载传输,如以太网(802.3,使用CSMA/CD)、无线局域网(802.11,使用CSMA/CA)、令牌环网(802.5)。
3

二、 IP数据报(IP分组)的格式

IP数据报由 首部数据部分 组成。首部包含固定部分(20字节)和可变部分(0~40字节)。

核心字段解析

  1. 版本(4bit):区分IP协议版本(如 IPv4、IPv6)。
  2. 首部长度(4bit)4B 为单位
    • 由于4bit最大能表示15,所以首部最大长度为 15 × 4B = 60B
    • 固定首部为20B,所以该字段的最小值为5(5 × 4B = 20B)。
  3. 总长度(16bit)1B 为单位。涵盖首部和数据部分的总和,最大理论值为 65535B
  4. 标识(16bit):由源主机生成,通常是一个自增序列。同一个原始数据报被分片后,所有分片的标识都相同。
  5. 标志(3bit)
    • DF (Don’t Fragment):次低位。DF = 1 表示不允许分片;DF = 0 表示允许分片。
    • MF (More Fragment):最低位。MF = 1 表示后面还有分片;MF = 0 表示这是最后一个分片。
  6. 片偏移(13bit):表示该分片的数据部分在“被分片前”的原始数据部分中的相对位置。8B 为单位
  7. 生存时间 TTL(8bit):数据报在网络中可通过的路由器数量的最大值(跳数)。
  8. 协议(8bit):指出此数据报携带的数据使用何种协议(例如:6表示TCP,17表示UDP)。
  9. 首部检验和(16bit):每个路由器仅校验首部,不对数据部分进行校验。
4
image-20260608120547811
image-20260608120635007

三、 IP数据报的“分片”问题

1. 根本原因与MTU

  • 数据链路层数据帧能承载的最大数据量称为 最大传输单元(MTU)(例如以太网的 MTU = 1500B)。
  • 当一个IP数据报的总长度超出了下一段链路的MTU时,就需要对该数据报进行分片。

2. 分片的核心规则

  • 发生位置:分片可能在源主机或传输路径上的任何一个路由器中发生。
  • 重组位置:只有目的主机才会对分片进行“重组”。
  • 传输特性:各分片作为独立IP数据报转发,可能通过不同路径乱序到达目的主机。
  • 长度约束(核心考点):由于片偏移字段以 8B 为单位,因此除了最后一个分片外,其他每个分片的数据部分长度必须是 8B 的整数倍
QQ20260605-181757

3. 经典分片计算实例

已知条件

原始IP数据报:总长度 = 4000B(首部 20B,有效数据 3980B),标识 = 777,MF = 0

下一段链路的 MTU = 1500B

  • 计算每个分片的最大数据载荷

    由于每个分片需要扣除 20B 的IP首部,剩余可传输数据空间为 1500 − 20 = 1480B

    检查 1480 是否能被 8 整除:1480 ÷ 8 = 185(符合 8B 整数倍规则)。

  • 分片拆分结果

分片编号 总长度 数据部分长度 数据区间 标识 MF DF 片偏移 (数据起始字节 ÷8)
分片 1 1500B 1480B 0 ∼ 1479 777 1 0 0 ÷ 8 = 0
分片 2 1500B 1480B 1480 ∼ 2959 777 1 0 1480 ÷ 8 = 185
分片 3 1040B 1020B (3980 − 1480 × 2) 2960 ∼ 3979 777 0 0 2960 ÷ 8 = 370
image-20260608120520741

四、 生存时间 TTL (Time To Live) 工作机制

  • 初始化:TTL的初始值通常由源主机设置。
  • 递减过程:数据报在网络中每经过一个路由器,路由器在转发前就会将 TTL 减 1
  • 异常处理
    • 如果路由器发现 TTL 减到 0,就会直接 丢弃该分组,不再进行转发。
    • 丢弃的同时,路由器会向源主机发送一个 ICMP 报文(超时差错报告),以此通知源主机发生了异常事件,防止无用分组在网络中无休止地循环。
image-20260608120711912

ip地址

为您整理的计算机网络核心笔记(关于IP地址分类、特殊IP地址、以及IP分组转发与网关配置):

一、 分类IP地址方案(Classful Addressing)

早期互联网将32位(32bit)IP地址划分为两级结构:IP地址 = <网络号>, <主机号>。通过检查IP地址的前几个比特(最高前缀),即可推断出网络号所占的位数。

  • A类地址(1 ~ 126)
    • 最高位固定为 0
    • 前8位为网络号,后24位为主机号。
  • B类地址(128 ~ 191)
    • 最高位固定为 10
    • 前16位为网络号,后16位为主机号。
  • C类地址(192 ~ 223)
    • 最高位固定为 110
    • 前24位为网络号,后8位为主机号。
  • D类地址(224 ~ 239):最高位固定为 1110,用作多播地址(组播)。
  • E类地址(240 ~ 255):最高位固定为 1111,保留供以后使用。

💡 核心常识

  1. 处于同一个物理网络的所有主机和路由器接口,其IP地址中的“网络号”必须完全相同
  2. 路由器与路由器连接的对等接口可以不分配IP地址(无编号链路);但路由器与普通网络节点连接的接口必须分配IP地址
  3. 当一台新主机接入网络时,除了分配唯一的IP地址,还必须配置“默认网关”
QQ20260608-123837

二、 特殊用途的IP地址

下表中的特殊IP地址不能指派给网络中的任何一台普通主机或路由器接口进行“私用”:

网络号 主机号 可作源地址? 可作目的地址? 代表的含义与实际用途
Y 全0 表示整个网络本身(只能用于路由表、转发表中作为网络前缀)。
Y 全1 ✔️ 直接广播地址。向网络号为 Y 的外部网络中所有主机广播IP分组。
0 Y ✔️ 表示本网络中主机号为 Y 的特定主机。
全0 全0 ✔️ 表示本网络上的本主机(常在主机刚启动、通过 DHCP 协议获取动态IP时作为源地址使用)。
全1 全1 ✔️ 受限广播地址。向当前本物理网络内的所有主机广播分组,路由器不会转发此广播。
127 任何数(非全0/1) ✔️ ✔️ 环回自检地址(Loopback)。表示主机本身,流量不流向网络,常用于本地网络软件环回测试。

📌 重要数量结论

由前两行可知,在具体的分类网络中,主机号全0(网络号)和主机号全1(广播地址)被特殊保留。因此,如果一个网络中主机号占 N bit,那么该网络最多支持的可用主机&路由器接口数为:

2N2

三、 IP分组转发与网关配置实例

结合学校与公司的拓扑图,IP分组在网络层的转发表现为两种场景:

1. 本地直接交付(同网段通信)

  • 场景:主机 H1(166.1.0.1) → H6(166.1.4.4)
  • 机制H1 检查目的IP,发现两者的网络号都是 166.1(同属学校的B类网段)。
  • 流程:不经过路由器,直接在本地通过 ARP协议 查询 H6 的 MAC 地址,并在数据链路层封装成帧直接发送。

2. 间接交付(跨网段通信与默认网关)

  • 场景:主机 H1(166.1.0.1) → H7(200.1.1.2)
  • 默认网关的作用H1 ∼ H6 必须配置默认网关为 166.1.0.5(即学校路由器的内网接口 B2)。
  • 转发流程
    1. H1 发现目的IP 200.1.1.2 属于外网,无法直接送达。
    2. H1 将该IP分组打包,并在底层通过网关的 MAC 地址,把分组投递给默认网关(学校路由器)
    3. 学校路由器剥离数据帧后提取IP分组,查阅自身的转发表
    4. 转发表匹配到目的网络号 200.1.1.0 对应的下一跳转发接口为 B0,随后将分组送往公司路由器 C0 接口,最终由公司路由器交付给 H7
QQ20260609-132829

子网掩码和子网划分

根据您上传的这一组关于子网划分、默认路由及数据报端到端发送/转发流程的课件,我为您提炼并整理了一份逻辑清晰、重点突出的复习笔记:

一、 子网划分技术与子网掩码

QQ20260609-151404

1. 原理与结构演变

  • 两级结构 三级结构
    • 划分前IP地址 = <网络号>, <主机号>
    • 划分后IP地址 = <网络号>, <子网号>, <主机号>
  • 划分机制:从原本属于主机号的 n bit 中抠出 k bit 作为子网号,剩余的 n − k bit 继续作为主机号。这样能划分出 2k 个大小相等的子网。
  • 特殊地址约束:每个子网地址中,主机号部分不能分配为全 0 或全 1
    • 全 0:代表该子网网络号本身。
    • 全 1:代表该子网的广播地址。

2. 子网掩码(Subnet Mask)

  • 核心作用:将子网掩码与IP地址进行“逐位相与(AND)”,即可快速算出 <网络号, 子网号>(即网络前缀)。只有网络前缀完全相同的IP地址,才归属于同一个子网
  • 默认子网掩码(未进行子网划分时的传统分类网络):
    • A类255.0.0.0
    • B类255.255.0.0
    • C类255.255.255.0
  • 配置规范
    1. 进行了子网划分的网络内部,每台主机、每个路由器接口都必须同时配置:IP地址、默认网关、子网掩码
    2. 路由器的转发表项必须升级为三元组:<目的网络号, 子网掩码, 转发接口>

二、 默认路由(Default Route)

  • 表项设置:目的网络号全 0(0.0.0.0),子网掩码全 0(0.0.0.0)。
  • 匹配机制:在路由器查转发表时,如果路由表中所有的常规表项都无法匹配成功,数据报最终就会命中默认路由并从指定的默认接口转发出去。

三、 IP 数据报端到端的传输全流程

QQ20260609-212156

1. 主机发送端处理逻辑(间接交付 vs 直接交付)

当主机准备发送一个 IP 数据报时:

  1. 网络前缀匹配:使用本机配置的子网掩码,分别与“本机IP”和“目的IP”进行逐位相与,检查两者的网络前缀是否相同。
  2. 决策路径
    • 若相同(属于同一网络):进行本地直接交付。通过 ARP协议 询问得到目的主机的 MAC 地址,直接在数据链路层封装成帧,通过交换机发送给目的主机。
    • 若不同(不属于同一网络):进行间接交付。通过 ARP协议 询问得到默认网关(路由器接口)的 MAC 地址,将帧发送给默认网关,由路由器帮其转发。
QQ20260609-212225

2. 路由器转发端处理逻辑

当路由器某个接口收到 IP 数据报时:

  1. 校验提取:对 IP 数据报首部进行校验,从中提取出目的 IP 地址
  2. 查转发表:依次将目的 IP 地址与转发表中各个表项的“子网掩码”进行逐位相与,看计算结果是否等于该表项的“目的网络号”。
  3. 执行转发:根据匹配成功的表项,将数据报从对应的转发接口送出。
    • 注:如果匹配出来的“转发接口”恰好与该数据报进入路由器的入口相同,则路由器直接就地交付,不用再将其转回。

四、 拓扑案例实战深度解析

QQ20260609-151353

结合课件中的学校拓扑图(B类地址段 166.1.x.x),我们可以观察到两种经典的通信轨迹:

案例 A:跨网段通信(间接交付)

  • 场景:主机 H1(166.1.0.1) → H3(166.1.128.2),网络子网掩码配置为 255.255.128.0
  • 路径解析
    1. H1 发现目的 IP 经掩码相与后得到的网络号是 166.1.128.0,与自己的网络号 166.1.0.0 不同。
    2. H1 将数据包转交给默认网关 166.1.0.5,通过接口 B3 进入学校路由器。
    3. 学校路由器查表,命中表项 166.1.128.0 | 255.255.128.0 | B2,数据包最终从接口 B2 送出,成功到达 H3
QQ20260609-210411

案例 B:同网段通信(本地直接交付)

  • 场景:主机 H3(166.1.128.2) → H6(166.1.150.1),子网掩码为 255.255.128.0
  • 路径解析(核心考点)
    • H3H6 的第三字节分别与掩码的 128 (100000002) 相与:
      • 128 AND 128 = 128
      • 150(100101102) AND 128(100000002) = 128
    • 结论:两者的网络前缀相同(同属 166.1.128.0 网段)。因此,H3H6 的数据帧直接通过本地交换机完成内部直接交付(如蓝线所示),根本不需要通过网关中转到路由器内部!
QQ20260609-210059

无分类编址(CIDR)Classless Inter-Domain Routing

QQ20260609-215026

一、 无分类编址 CIDR 核心概念

1. 时代背景与定义

  • 背景:1993年提出,消出了传统的 A/B/C/D/E 类网络划分,大幅提升了IP地址的利用率,缓解了IPv4地址枯竭危机。

  • 两级结构

    IP地址 = {⟨网络前缀⟩, ⟨主机号⟩}

    其中网络前缀的长度是可变的

  • 斜线记法(CIDR记法):如 128.14.32.153/30,表示网络前缀占 30 bit,主机号占 2 bit

2. 实例拆解:128.14.32.0/21 地址块

  • 主机号位数32 − 21 = 11 bit

  • 最大可分配主机数211 − 2 = 2046 台(可满足某单位2000台主机的联网需求)。

  • 子网掩码:前21位全为1,后11位全为0。

    二进制:11111111.11111111.11111000.00000000 → 点分十进制:255.255.248.0

  • 地址块范围计算

    保持前21位不变,将后11位主机号分别置为全0(起点)和全1(终点):

    • 起始地址128.14.32.0(主机号全0,代表本网络号)
    • 结束地址128.14.39.255(主机号全1,代表广播地址)
QQ20260609-213405

二、 子网划分方案对比:定长 vs 变长

1. 定长子网划分(FLSM)

  • 原理:将固定长度的主机号前 k bit 抠出来作为子网号,划分出 2k大小完全相同的子网。
  • 缺陷:极不灵活。当各个物理网络实际需要的主机数量差距悬殊时,会导致大网不够用、小网严重浪费,IP地址利用率极低。

2. 变长子网划分(VLSM)

  • 原理:在CIDR地址块内部动态划分物理网络时,子网号的长度不固定
  • 优势:根据每个子网的实际需求“量体裁衣”分配对应大小的地址块(如一、二级ISP之间的层层动态下发)。

三、 CIDR 子网划分的硬性约束

  • 主机号禁用规则:在任何一个划分出的子网中,主机号全0(网络本身)和主机号全1(广播)的IP地址绝对不能分配给具体的节点私用

  • 最小子网限制(点对点链路)

    对于只有两台设备互联的“点对点链路”(如路由器与路由器、或路由器与单台主机“狗剩”连接):

    • 结论:主机号至少需要保留 2 bit
    • 原因:如果只留 1 bit,则只能表示 01,全部被全0/全1规则抹杀,导致可用IP为0。保留 2 bit 时,可选有 00, 01, 10, 11,去掉 0011 后,刚好剩下 0110 两个合法IP分配给链路两端。因此,点对点链路对应的最高效CIDR前缀为 /30

四、 核心解题技巧:二叉树划分法

在面对复杂的变长子网划分题型时,可以利用“从根到叶构造二叉哈夫曼树”的技巧快速解题:

1. 构造规则

  1. 根节点:代表原始获得的最大的 CIDR 地址块(假设其可自由分配的主机号占 h bit)。
  2. 分支规则:向下每分裂一次代表借用 1 bit 作子网号。规定左分支写 0,右分支写 1(反之亦可)。
  3. 叶子节点:树的末端叶子结点即对应一个划分出的子网。
  4. 路径锁定:从根节点到达该叶子结点的路径二进制编码,紧跟在原始网络前缀后面,组成该子网新的网络前缀。

2. 实战演练(根据拓扑图)

假设咸鱼电信获得原始块:128.14.32.128/27(共32个IP,可分配主机位 5 bit)。

QQ20260609-214621

路由聚合

路由聚合(Route Aggregation / 构成超网)

1. 概念与原理

  • 定义:在一个路由器转发表中,如果几条路由表项的转发接口相同,且它们的部分网络前缀也相同,就可以将这几条路由表项压缩、合并为一条表项。这种地址的合并技术称为路由聚合,也叫构成超网(Supernetting)
  • 聚合方法:将各网络的网络前缀转换成二进制,寻找它们共同的前缀部分。共同的位数即为聚合后的新网络前缀长度。

2. 课件实例解剖

县里的电信路由器原本有三条去往不同子网的路由,由于转发接口都是 G1,可以进行聚合:

  • 表项① 128.14.32.128/2810000000.00011110.00100000.10000000
  • 表项② 128.14.32.144/2910000000.00011110.00100000.10010000
  • 表项③ 128.14.32.152/3010000000.00011110.00100000.10011000

🔍 找共同点:可以看到,前27位(即第四字节的前三位 100)完全相同。

聚合后的表项④ 128.14.32.128/27,转发接口仍为 G1

QQ20260609-221846

3. 优缺点对比

  • 优点表更小,查询更快。能极大地减少路由表的大小,降低路由器的内存消耗和查表延迟。
  • 缺点可能会引入额外的无效地址
    • 例如:聚合后的 /27 地址块涵盖了 128.14.32.156/30。如果这个地址块在实际中是空闲的/无效的,那么当有去往该地址的数据报时,也会被错误地派发到 G1 接口,从而带来了额外的无效流量。

二、 最长前缀匹配原则(Longest Prefix Match)

1. 核心思想

在使用 CIDR 编制时,由于路由聚合的存在,转发表中的一条目的 IP 地址可能会同时匹配上多个不同的路由表项。

  • 最长前缀匹配原则:当有多个表项都能匹配成功时,路由器会选择网络前缀最长(即掩码中1的个数最多)的那个表项
  • 深层逻辑:网络前缀越长,说明其划分的子网范围越小,路由就越具体(Specific)、越精确。

2. 经典查表匹配实例

已知场景:县里的电信路由器收到一个数据报,其目的 IP 地址 = 128.14.32.131

目的 IP 对应的二进制:10000000.00011110.00100000.10000011

将目的 IP 与路由转发表中的各项逐一进行前缀比对:

  1. 比对表项①(128.14.32.128/27 | 接口 G1)
    • 检查前 27 位:10000000.00011110.00100000.100...
    • 结果:匹配成功(前27位完全一致)。
  2. 比对表项②(128.14.32.128/28 | 接口 G3,对应铁柱网吧)
    • 检查前 28 位:10000000.00011110.00100000.1000...
    • 结果:匹配成功(前28位完全一致)。
  3. 比对表项③(0.0.0.0/0 | 接口 G0,默认路由)
    • 默认路由匹配长度为 0 位。
    • 结果:匹配成功(作为兜底选项)。

3. 最终转发决策

  • 匹配成功的表项有:表项①(27位)、表项②(28位)、表项③(0位)。
  • 根据最长前缀匹配原则,表项②的 28 位匹配长度最长。
  • 结论:最终路由器命中表项②,将该 IP 数据报从 G3 接口 转发出去,精准送达铁柱网吧。
QQ20260609-222212

网络地址转换NAT

QQ20260609-231437

一、 NAT(网络地址转换)核心概念

1. 私有 IP 地址(内网 IP)

  • 划分网段
    • 10.0.0.0 ~ 10.255.255.255
    • 172.16.0.0 ~ 172.31.255.255
    • 192.168.0.0 ~ 192.168.255.255
  • 应用规则:只允许分配给局域网内部的节点,不允许直接分配给互联网上的节点
  • 复用特性:私有 IP 地址是可复用的。只需在同一个局域网内保持唯一即可,不需要全球唯一。

2. 全球 IP 地址(外网 IP)

  • 来源:通常由因特网服务提供商(ISP)提供。
  • 特性全球唯一。是局域网与外界互联网通信时真正使用的公共 IP 地址。

二、 NAT 路由器 vs 普通路由器

设备类型 核心工作层级 是否改变 IP/端口 核心机制 / 功能
普通路由器 仅包含网络层及以下功能 绝不改变 仅根据转发表中的目的网络号,规划路径并原封不动地转发 IP 数据报。
NAT 路由器 包含传输层功能(需识别端口) ✔️ 主动更改 在转发 IP 数据报时,进行内网 IP/端口 与 外网 IP/端口 的相互转换。

📌 NAT 表的记录格式

NAT 表维护着动态的转换映射关系:〈内网 IP : 端口号〉 ↔︎ 〈外网 IP : 端口号〉

三、 NAT 路由器转换规则

  • 从内网转发到外网(Outbound)
    • 路由器会更改数据报的 源 IP 地址源端口号(将内网私有地址改写为路由器的外网公共地址与新分配的端口)。
  • 从外网转发到内网(Inbound)
    • 路由器会更改数据报的 目的 IP 地址目的端口号(根据外网端口反查 NAT 表,还原为对应的内部主机私有地址与进程端口)。

四、 实战轨迹拆解:手机1 ↔︎ 手机2 微信通信全流程

结合课件中的“训练二”(手机1向手机2发送微信图片),我们通过数据报在不同阶段的首部字段变化来闭环理解 NAT 转换:

1. 角色基本配置

  • 发送端(右侧局域网)
    • 手机1 内网 IP:192.168.3.48,微信进程端口:9855
    • 右侧 NAT 路由器外网 IP:59.175.49.153
    • 右侧 NAT 表项:59.175.49.153 : 7788 ↔︎ 192.168.3.48 : 9855
  • 接收端(左侧局域网)
    • 手机2 内网 IP:192.168.3.74,微信进程端口:6666
    • 左侧 NAT 路由器外网 IP:66.211.88.55
    • 左侧 NAT 表项:192.168.3.74 : 6666 ↔︎ 66.211.88.55 : 4096

2. 数据报三阶段字段演变

阶段一:数据报刚离开手机1,在右侧内网中传输时

此时数据报尚未经过右侧 NAT 路由器:

  • IP 首部:源 IP = 192.168.3.48,目的 IP = 66.211.88.55(指向接收端的公网地址)
  • TCP 首部:源端口 = 9855,目的端口 = 4096(指向接收端在公网映射的端口)
QQ20260609-235934
阶段二:数据报经过右侧 NAT 转换,在互联网(Internet)中传输时

右侧 NAT 路由器根据自身 NAT 表,将源信息改写为外网公共信息:

  • IP 首部:源 IP = 59.175.49.153(被改写),目的 IP = 66.211.88.55
  • TCP 首部:源端口 = 7788(被改写),目的端口 = 4096
QQ20260610-000026
阶段三:数据报到达左侧 NAT 路由器,转换后进入左侧内网时

左侧 NAT 路由器收到外网数据报,查阅自身 NAT 表,将目的信息还原为左侧内网特定主机的进程信息:

  • IP 首部:源 IP = 59.175.49.153,目的 IP = 192.168.3.74(被还原)
  • TCP 首部:源端口 = 7788,目的端口 = 6666(被还原)
  • 最终结果:手机2 的微信进程(6666端口)成功收到由公网 59.175.49.153:7788 投递过来的图片数据。
QQ20260610-000107

地址解析协议(ARP)Address Resolution Protocol

ARP 协议基础概念

QQ20260610-003006

1. 硬件地址与网络层地址的关系

  • MAC 地址(48bit):是网络适配器(网卡)出厂时分配好的硬件地址,具有全球唯一性
    • 一台主机或节点至少拥有一个网络适配器,因此至少有一个 MAC 地址
    • 路由器作为一个三层设备,拥有多个用于数据转发的独立接口,每个接口背后都是一个独立的网络适配器,因此路由器拥有多个不同的 MAC 地址
  • ARP 协议的核心作用:在同一个局域网(LAN)内部,动态地建立并查询一个已知的 IP 地址与对应的硬件 MAC 地址之间的映射关系。

2. ARP 表(ARP 缓存)

  • 数据结构:每台主机和路由器的每个转发接口都维护着自己独立的 ARP 高速缓存表,专门记录 〈IP地址 ↔︎ MAC地址〉 的映射对。
  • 生存特性:为了适应网络拓扑的动态变化(如设备更换、IP 重新分配),ARP 表项需要定期进行自动更新和老化清除

ARP 交互工作流程(请求与响应机制)

当主机 A 需要向本局域网内的目标主机 B 发送数据,但其本地 ARP 表中缺失 B 的 MAC 地址时,将触发以下双向交互流程:

1. ① ARP 请求分组(ARP Request)—— 广播发送

  • 具体报文内容
    • 我是谁? 我的 IP 地址是 X,我的 MAC 地址是 Y
    • 我想找谁? 我想找的那个家伙,IP 地址是 Z
  • 链路层封装:封装入 MAC 帧时,帧目的地址设为全 1(广播)。
  • 传输特性:作为广播帧发送,局域网内的所有主机和路由器接口均能接收并解封装该帧,并读取里面的 ARP 请求。

2. ② ARP 响应分组(ARP Reply)—— 单播回传

  • 具体报文内容
    • 自我认领 “你好,我就是你要找的那个靓仔,我的 IP 地址是 Z,我的 MAC 地址是 V。”
  • 链路层封装:封装入 MAC 帧时,帧目的地址显式设为 Y(原请求方的 MAC 地址),源地址设为 V
  • 传输特性:作为单播帧精准回传给请求方,网络内其他无关主机在链路层发现目的 MAC 不匹配时会自动丢弃该帧。

拓扑实战训练案例推导

结合课件中的网络拓扑图,该网络使用了 CIDR 划分方案,划分为两个大子网(网络前缀为 /17):

  • 接口 B3166.1.0.5/17(覆盖 H1, H2 等,范围 166.1.0.1 ~ 166.1.127.255
  • 接口 B2166.1.128.1/17(覆盖 H3 ∼ H6 等,范围 166.1.128.1 ~ 166.1.255.255
    • 关键推导H3166.1.128.2)与 H6166.1.150.1)经掩码计算后,网络前缀均为 166.1.128.0,因此它们处于同一个局域网物理网段内
QQ20260610-082047

假设 H3 想给默认网关(166.1.128.1)发送 IP 数据报,分析 ARP 工作过程

  1. 判断路径H3 发现目的 IP 166.1.128.1 与自己在同一网段,属于本地直接交付,但本地无网关的 MAC 地址。
  2. H3 发起请求H3 产生一个 ARP 请求分组
    • 内容:我是谁:166.1.128.2 , AA:BB:CC:DD:EE:33;我找谁:166.1.128.1
    • 封装:MAC 帧目的地址 = FF-FF-FF-FF-FF-FF(全1广播),源地址 = ...EE:33
  3. 路由器接口响应:学校路由器的 B2 接口收到广播并提取匹配,发现寻找的是自己,于是向 H3 单播回传一个 ARP 响应分组
    • 内容166.1.128.1 对应的 MAC 地址是 AA:BB:CC:DD:EE:B2
    • 封装:MAC 帧目的地址 = ...EE:33(单播),源地址 = ...EE:B2
  4. 状态学习H3 收到响应后,将其写入自己的 ARP 表中。

这份为您精心整理的动态主机配置协议 DHCP (Dynamic Host Configuration Protocol) 核心网络笔记,严格基于您提供的课件图解进行结构化提炼,直击考点与底层协议字段变化:

动态主机配置协议 DHCP Dynamic Host Configuration Protocol

一、 DHCP 基本概念与体系结构

1. 核心作用

  • 为刚刚接入网络的计算机动态分配 IP 地址
  • 自动配置主机的核心网络参数:默认网关、子网掩码、DNS 服务器地址等。

2. 架构模式:客户端/服务器模型 (C/S)

  • DHCP 客户端:新接入网络、希望获得 IP 地址等配置信息的主机。
  • DHCP 服务器:负责管理 IP 地址池并分配 IP 地址的专用主机或设备。
    • 注:在家庭网络中,DHCP 服务器通常由家庭路由器兼职;在大型企业/校园网络内,可以同时存在多台专用 DHCP 服务器。

3. 协议层级与端口分配

  • DHCP 属于 应用层协议
  • 底层传输依赖 UDP 协议 进行不可靠传输。
    • 客户端 UDP 端口号 = 68
    • 服务器 UDP 端口号 = 67
QQ20260610-082550

二、 DHCP 协议数据的层层封装流程

当 DHCP 报文从应用层向下传递时,会历经操作系统的网络栈进行层层头部包装:

  1. 应用层:生成原始的 DHCP 报文(包含配置载荷数据 H5)。
  2. 传输层:添加 UDP 首部(H4),填入源/目的端口号,封装为 UDP 数据报
  3. 网络层:添加 IP 首部(H3),填入源/目的 IP 地址,封装为 IP 数据报
  4. 数据链路层:添加 MAC 帧头(H2)和帧尾(T2),封装为可直接在物理链路上传输的 MAC 帧
  5. 物理层:将 MAC 帧转换为纯粹的 二进制比特流10100...)在介质中发射。

三、 DHCP 四步交互工作原理 (DORA 过程)

当新主机 H3(MAC 地址为 AA:BB:CC:DD:EE:33)初次接入网络,与网络中的 DHCP 服务器(IP 为 166.1.128.8,MAC 为 AA:BB:CC:DD:EE:08)交互时,会完整经历以下四个阶段:

QQ20260610-094110

1. ① DHCP 发现报文 (DHCP DISCOVER) —— 客户端 服务器

  • 核心语义:客户端“初来乍到”,在网络中大喊:“有谁能给我发个 IP 地址吗?我的 MAC 地址是 … ”。
  • 各层头部字段配置
    • 传输层 (UDP):源端口 = 68,目的端口 = 67
    • 网络层 (IP):源 IP = 0.0.0.0(此时主机尚无 IP),目的 IP = 255.255.255.255本地广播,发送给网络内的所有人)
    • 数据链路层 (MAC):源 MAC = ...EE:33,目的 MAC = FF-FF-FF-FF-FF-FF广播帧

2. ② DHCP 提供报文 (DHCP OFFER) —— 服务器 客户端

  • 核心语义:DHCP 服务器收到广播后,从地址池预留一个 IP,对客户端说:“我有空闲 IP 给你,这个 166.1.128.2 租给你用 24 小时,掩码和网关也给你准备好了。”
  • 各层头部字段配置
    • 传输层 (UDP):源端口 = 67,目的端口 = 68
    • 网络层 (IP):源 IP = 166.1.128.8,目的 IP = 255.255.255.255广播数据报
    • 数据链路层 (MAC):源 MAC = ...EE:08,目的 MAC = AA:BB:CC:DD:EE:33单播帧
    • 深度思考:为什么此时 IP 层用广播而 MAC 层可以用单播?因为客户端网卡能识别自己的硬件 MAC 地址,从而在链路层精准接收该单播帧;但由于客户端在网络层还没有正式绑定合法的 IP,无法作为常规 IP 单播终点,故网络层依然设置为广播形式。

3. ③ DHCP 请求报文 (DHCP REQUEST) —— 客户端 服务器

  • 核心语义:客户端向全网宣布:“我正式接受来自服务器 166.1.128.8 提供的 IP 地址 166.1.128.2!”

  • 各层头部字段配置

    • 传输层 (UDP):源端口 = 68,目的端口 = 67

    • 网络层 (IP):源 IP = 0.0.0.0(正式确立前仍暂用0),目的 IP = 255.255.255.255本地广播

    • 数据链路层 (MAC):源 MAC = ...EE:33,目的 MAC = FF-FF-FF-FF-FF-FF广播帧

    • 核心考点:为什么这一步明明已经有了心仪的 IP,却还要用广播?

      原因是为了通知全网所有的 DHCP 服务器。如果网络中存在多台 DHCP 服务器且它们都发了 OFFER,客户端只能选一个。通过广播 REQUEST,既能明确告知被选中的服务器“我要用你的 IP”,也能让其他未被选中的服务器获知消息,及时释放它们此前为该客户端预留的备用 IP 地址。

4. ④ DHCP 确认报文 (DHCP ACK) —— 服务器 客户端

  • 核心语义:选中的 DHCP 服务器做出最终批复:“没问题,一锤定音,这地址你正式拿去用吧!”
  • 各层头部字段配置
    • 传输层 (UDP):源端口 = 67,目的端口 = 68
    • 网络层 (IP):源 IP = 166.1.128.8,目的 IP = 255.255.255.255广播数据报
    • 数据链路层 (MAC):源 MAC = ...EE:08,目的 MAC = AA:BB:CC:DD:EE:33单播帧
  • 后续动作:客户端收到 ACK 后,正式将 166.1.128.2 配置到本地网卡上,此时主机才真正拥有了在网络中合法合规进行跨网段双向通信的能力。

网际控制报文协议 ICMP(Internet Control Message Protocol)

QQ20260610-094952

一、 ICMP 概述与封装格式

1. 协议定位与核心功能

  • 协议层级:ICMP 属于 网络层协议
  • 核心作用:由于 IP 协议本身提供的是不可靠、无连接的尽力交付服务,ICMP 专门用于让主机或路由器互相报告网络中发生的差错和异常情况,以提高交付成功的机会。

2. 底层封装格式

  • 层层嵌套:ICMP 报文并不是直接交付给数据链路层,而是作为数据部分封装在 IP 数据报中
  • IP 首部相关关键字段
    • 协议(Protocol)字段 = 1:明确指示该 IP 数据报的数据部分是 ICMP 报文。
    • 携带源 IP 地址、目的 IP 地址、TTL、首部校验和等信息。
  • ICMP 报文自身结构:其首部的前 8 bit 为 Type(类型)字段,用于在底层直接指明该 ICMP 报文的具体功能类型。
QQ20260610-095619

二、 ICMP 报文分类与深度解析

ICMP 报文主要分为两大类:差错报告报文询问报文

1. 差错报告报文(Error Reporting)

① 终点不可达 (Destination Unreachable)

当路由器或目的主机无法交付数据报时发送。根据触发主体的不同分为两种常考场景:

  • 路由器发送(网络/主机不可达):当路由器找不到去往目的网络的路由线索时(道路不通),丢弃数据报并向源主机报告。
  • 目的主机发送(端口不可达):数据报已经成功送达目的主机,但目的主机发现传输层对应的目的端口号不存在(本地没有对应的监听进程),由目的主机向源主机报告。
QQ20260610-100642

② 时间超过 (Time Exceeded)

  • 路由器发送(传输途中超时):数据报在网络中传输时,每经过一个路由器其 TTL 减 1。一旦路由器发现 TTL = 0,则直接丢弃该数据报,并向源主机发送时间超过报文(说明路程太远或出现路由环路)。
  • 目的主机发送(分片组装超时):当一个大 IP 数据报被分片传输时,如果部分分片已到达目的主机,但在规定时间内其余分片未能齐备,目的主机将无法重组,只能全部丢弃已到分片,并向源主机发送时间超过报文。

③ 参数问题 (Parameter Problem)

  • 发送主体:路由器或目的主机。
  • 触发条件:当发现收到的 IP 数据报首部中的某些参数不合法,或者首部校验和出现差错时,丢弃该数据报并向源主机报告。

④ 改变路由 / 重定向 (Redirect)

  • 发送主体:局域网内的路由器。
  • 触发场景:主机 A 本应将去往目的地 C 的数据报直接发送给更近的路径,但却错误地发给了默认路由器。默认路由器在帮其转发的同时,会向 A 发送重定向报文,提示:“对于这个目的网络,下次你直接让另一台路由器帮你转发,路径会更短”,以此让主机动态更新路由表

注:源点抑制报文(用于通知源端网络拥塞、请求降低发送速率)在 2012年后已被废弃

2. 询问报文(Query)

① 回送请求与回答 (Echo Request / Reply)

  • 交互逻辑:源主机向目的主机发送回送请求(“在吗?请回答我”),目的主机收到后必须原样回传回送回答(“在!”)。
  • 用途:用于测试目的站是否可达以及了解其有关状态。

② 时间戳请求与回答 (Timestamp Request / Reply)

  • 交互逻辑:请求当前网络的绝对时间。
  • 用途:用于进行网络时钟同步和测量传输时延。

三、 不必反馈 ICMP 差错报告的特殊情况(高频考点)

为了防止网络中出现 ICMP 报文无限循环并导致信道拥塞,以下 4 种情况绝对不发送 ICMP 差错报告报文:

  1. 差错中的差错不报:如果出错的数据报本身就是携带着 ICMP 差错报告报文 的 IP 数据报,则不再反馈差错。
  2. 分片只报第一个:如果一个 IP 数据报被分片传输,那么只有第一个分片出错时才会触发反馈,后续的其余分片出错时一律不予反馈。
  3. 特殊目的地址不报:对具有多播(组播)地址广播地址的 IP 数据报,一律不发送差错报告。
  4. 特殊源地址不报:如果 IP 数据报的源地址是特殊地址(如环回自检地址 127.x.x.x 或未知源地址 0.0.0.0),即便发生异常也不反馈。

四、 ICMP 的典型应用场景

1. ping 命令

  • 底层机制:直接基于 ICMP 询问报文中的 回送请求(Echo Request)回送回答(Echo Reply) 实现。
  • 工作层级ping 属于应用层直接使用网络层 ICMP 的典型例子,不经过传输层(不使用 TCP 或 UDP)

2. traceroute (Windows 下为 tracert) 命令

  • 底层机制:主要基于 ICMP 差错报告报文中的 时间超过报文 来探测去往目的主机所经过的整条路由器路径。
  • 实现原理
    1. 源主机向目的主机发送一系列 IP 数据报,故意将第一个数据报的 TTL 设为 1
    2. 第一跳路由器收到后 TTL 减 1 变为 0,被迫丢弃并回传一个 ICMP 时间超过报文,源主机由此获知第一跳路由器的 IP。
    3. 随后源主机将 TTL 逐次递增为 2, 3, 4... 发送,依次诱发沿途各级路由器回传超时报文,直到最终数据报安全到达目的主机(通常通过触发目的端口不可达报文来收尾),从而拼接出完整的网络拓扑路径。

IPv6

一、 IPv6 的诞生背景与优势(与 IPv4 对比)

特性维度 IPv4 IPv6
诞生年份 1981年 1998年
地址位数 32 位(32 bit) 128 位(128 bit)
地址总数 232 ≈ 42 亿个 2128 ≈ 3.4×1038
人均分配量 全球人均不足 1 个 全球人均可得 4.3 × 1028
根本解决问题 依赖 1994 年诞生的 NAT 技术缓解耗尽危机 彻底解决 IP 地址不够用的问题
应用时代 传统互联网时代 万物互联(IoT)时代(让每个传感器、电器都拥有全球唯一IP)
QQ20260610-111225

二、 IPv6 地址格式与基本记法

  • 记法名称冒号十六进制记法
  • 划分规则:总共 128 bit,每 16 bit 为一段(共 8 段),每段转换为 4 位十六进制数,段与段之间用冒号(:)分隔。
  • 二进制对照示例
    • 二进制00100001000011011000...(共128位)
    • 标准十六进制表现形式2001:0db8:85a3:0000:0000:8a2e:0370:7334

三、 核心考点:IPv6 地址的压缩记法

为了简化冗长的 IPv6 地址,有两条严格的压缩改写规则:

  1. 省略前导零:每个 16 位分段内部的前导零可以省略,但如果全为 0 则必须保留一个 0
  2. 双冒号“::”替代:可使用双冒号 :: 替代连续出现的多个全 0 段
    • ⚠️ 极重要约束一个地址块中双冒号“::”只能出现一次,否则在恢复全零时会产生位数歧义。

📝 压缩改写实例闭环演练

  • 未压缩的原始地址2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • 第一步:去除各分段前导零 2001:db8:85a3:0:0:8a2e:370:7334
  • 第二步:双冒号替代连续全零段 2001:db8:85a3::8a2e:370:7334

四、 IPv6 地址分类表(高频选择题考点)

一台主机的完整 IPv6 地址结构表现为两级:IP地址 = 【n 位网络前缀, 128-n 位接口标识符】。主要分类映射关系如下:

地址类型 二进制前缀 CIDR 记法形式 含义与实际用途
未指明地址 全 0(128位) ::/128 表示“无地址”,类似于 IPv4 中的 0.0.0.0
环回地址 前127位全0,最后1位为1 ::1/128 类似 IPv4 的 127.0.0.1,用于本地软件环回自检测试。
多播地址 11111111(8位) FF00::/8 发送到一组属于特定多播组的主机(类似于群号)。
本地链路单播地址 (Link-Local) 1111111010(10位) FE80::/10 局域网内通信使用,该地址的数据报绝不会被路由器转发
全球单播地址 除上述四种外的其他所有地址 相当于公网 IP,可全球路由转发。

五、 IPv6 目的地地址的三种基本类型

IPv6 协议取消了 IPv4 中的广播概念,全面升级为以下三种目的地址通信模式:

  1. 单播(Unicast):传统的点对点通信。
  2. 多播(Multicast)一点对多点的通信。数据报会发送到一组计算机中的每一台。
    • 典型应用:网络视频会议、多播流媒体。
  3. 任播(Anycast)IPv6 新增的高级类型。目的地是一组计算机,但数据报只交付给其中距离最近的一台计算机
    • 典型应用:多个地理位置分散的 DNS 服务器共享同一个任播地址,引导用户就近访问。
    • 注:任播地址没有固定的专属前缀,通常由地址管理机构从单播地址块中预先统一分配。

六、 IPv6 地址资源的分配与“即插即用”特性

1. 灵活的资源块级联

IPv6 完美支持无分类编址 CIDR。地址层级通常划分为:[前缀 (n bit) , 子网标识符 (m bit) , 接口标识符 (128-n-m bit)]

  • 分配链路:一级 ISP 二级 ISP(如北京电信) 某学校局域网。
  • 标准校园网前缀示例2001:0db8:abcd:0012::/64(前缀占 64 bit,留 64 bit 给接口标识符)。

2. 即插即用(无状态自动配置 - SLAAC)

  • 工作机理:由于 IPv6 局域网的接口标识符位数(通常为 64 bit)非常长,一台新主机接入网络后,只需向本地路由器确认本网络的前缀信息,主机便可利用自身的 MAC 地址自动计算并拼接出全球唯一的接口标识符,从而自行生成合法的 IPv6 地址。
  • 对 DHCP 的影响:基于即插即用特性,IPv6 可以在完全不部署 DHCP 服务器的情况下实现全自动联网。但在一些对网络安全性、审计要求极高的特定场所,依然会配合使用 DHCPv6 进行地址的有状态统一集中管理。

路由协议

QQ20260610-134413

一、 路由协议与路由算法概述

  • 路由器的核心功能:转发 IP 数据报。
  • 路由协议的定义:用于规定路由器之间如何交换必要的网络拓扑或状态信息。
  • 路由算法的本质
    • 路由器之间交换的信息将作为路由算法的输入
    • 路由算法通过计算,输出最佳转发路径,从而最终生成路由器的路由表
    • 其数学本质是求解图的最短路径问题(Shortest Path Problem)。
  • 大纲要求掌握的三大路由协议RIP 协议、OSPF 协议、BGP 协议

二、 路由算法的分类

根据能否随网络的通信量或拓扑结构自适应地进行调整变化,路由算法可分为两大类:

  1. 静态路由算法(Static Routing)
    • 机制:由网络管理员手工配置每一条路由信息。
    • 特点:实现简单、开销小,但不具备自适应能力。
    • 适用场景:小型、结构稳定的网络。
  2. 动态路由算法(Dynamic Routing)
    • 机制:路由器根据网络流量负载和拓扑结构的变化,通过路由算法动态调整自身的路由表。
    • 特点:实现复杂、开销大,但具备良好的自适应能力。
    • 适用场景:大型、多变的网络。
    • 核心两大动态路由算法距离-向量路由算法(对应 RIP 协议)和链路状态路由算法(对应 OSPF 协议)。

三、 距离-向量路由算法(Distance-Vector Routing)

1. 核心思想与算法基础

  • 基于 Bellman-Ford(贝尔曼-福特)算法 的核心思想实现。
  • “距离”的定义:不同的路由协议定义不同。例如,RIP 协议中规定直接相连的两个节点之间距离(跳数)= 1;也可以将“往返时延”作为链路的距离。

2. 数学递推公式

假设 dx(net1) 表示从当前节点 x 到目的网络 net1 的最短距离(最小代价),则有:

dx(net1) = minv{c(x, v) + dv(net1)}

  • 参数解析
    • v:节点 x所有邻居节点(共 k 个邻居,如 v1, v2, v3...vk)。
    • c(x, v):节点 x 到其特定邻居 v 之间的直接链路距离
    • dv(net1):邻居 v 到目的网络 net1最短距离

3. 向量传递机制

  • 每个邻居节点都会定期向中间路由器 X 发送一个“距离-向量”(即该邻居到一片区域内每个目的网络的已知最短距离集合)。
    • 例如:邻居 v1 传来的向量为:去往 ⟨net1, net2, net3⟩ 的最短距离 = ⟨4, 6, 3⟩
    • 邻居 v2 传来的向量为:去往 ⟨net1, net2, net3⟩ 的最短距离 = ⟨3, 9, 10⟩
  • 路由器 X 收到所有邻居的向量后,结合自己到邻居的直接开销 c(x, v),利用上述公式计算出自己去往各个网络代价最小的路径,更新并维护自己的路由表。
QQ20260610-134105
  • 对应协议OSPF 协议的底层核心算法。
  • 核心要求:要求每台路由器节点都了解完整的网络拓扑结构。也就是说,每个节点都必须知道全网共有多少个节点、哪些节点是相连的、各条链路的代价(权值)是多少。
  • 对应数据结构带权图(Weighted Graph)。
  • 核心算法机制:只要一台路由器通过链路状态洪泛拿到了完整的带权图结构,就可以在本地调用 “迪杰斯特拉(Dijkstra)算法”,以自身为源点,计算出到达全网各个目的网络的最短路径,进而生成最优的转发表项。

路由选择

一、 为什么需要分层次的路由选择?

  • 根本矛盾:全世界的路由器数量、网络数量巨大(以亿计),没有任何一台路由器能拥有并维护全网完整的拓扑图或路由表(否则路由表过大,查表极慢,且占满设备内存)。
  • 解决方案:引入分层次的路由选择机制。将全球网络划分为技术上独立的区域,并在“区域内部”和“区域之间”使用不同级别的路由协议。

二、 自治系统 AS (Autonomous System)

  • 定义:指将全世界的因特网划分为多个在技术和行政上相互独立的管理区域。例如,由咸鱼电信管辖的网络区域可以申请成为一个 自治系统 AS
  • 技术决策权:AS 的管理单位有权自行决定在本自治系统内部,使用何种内部路由协议(如 RIP 或 OSPF)。
  • 层级关系自治系统之间是平级关系,不存在上下级的包含关系。

📌 AS 的关键特征(考点拓展)

  1. 全球唯一编号:每个自治系统都拥有一个全球唯一的 AS 编号(ASN),需要向互联网管理机构(如由 ICANN 授权的机构)统一申请。目前全球已接近 8 万个自治系统。
  2. CIDR 地址块组合:一个自治系统通常包含一个或多个 CIDR 地址块。这样的设计非常便于在该自治系统的边界处进行路由聚合,减少通告给外网的路由条目。
QQ20260610-135336

三、 路由协议的两大阵营:IGP 与 EGP

因特网将路由选择协议明确划分为两大类:

1. 内部网关协议 IGP (Interior Gateway Protocol)

  • 作用范围:在一个自治系统(AS)内部进行路由选择。
  • 经典代表
    • RIP 协议:基于距离-向量路由算法(网络数量越多,距离向量越大)。
    • OSPF 协议:基于链路状态路由算法(要求 AS 内每台路由器建立本系统的完整拓扑图)。
  • 形象比喻:相当于本地交流所用的“地方方言”。不同 AS 的内部方言互不影响。

2. 外部网关协议 EGP (External Gateway Protocol)

  • 作用范围:在不同的自治系统(AS)之间进行路由选择。
  • 当前标准BGP 协议(如目前广泛使用的 BGP-4)。
  • 形象比喻:相当于不同区域之间沟通所采用的“普通话”。

💡 术语历史注脚:在早期 TCP/IP 文献中,“网关(Gateway)” 便是路由器的代称,该术语沿用至今(如“默认网关”即为本地路由器的内网接口)。

四、 边界路由器与域间/域内路由选择

  • 自治系统边界路由器(Border Router):每个 AS 至少需要有一台边界路由器与其他自治系统相连。由于需要承载和计算全网的跨系统路径,边界路由器通常性能强悍、价格高昂
  • 边界路由器之间的通信:各边界路由器之间统一使用 BGP 协议 进行跨系统的路由信息交换。
  • 概念区分
    • 域内路由选择(Intra-domain routing):自治系统内部的路由选择。
    • 域间路由选择(Inter-domain routing):自治系统之间的路由选择。

路由信息协议 RIP(Routing Information Protocol)

一、 RIP 协议的核心规定与表项格式

1. 距离度量方式:跳数 (Hop Count)

  • 定义:RIP 使用跳数(也称距离)来衡量到达目的网络的远近。
  • 计算规则
    • 路由器到其直连网络的距离规定为 1
    • 每经过一个路由器进行转发,跳数就 加 1
  • 最优路径判定:RIP 算法非常单纯,它认为好的路由就是跳数最少的路由(即使该路径带宽极低)。

2. 网络规模限制(核心考点)

  • 最大合法距离:RIP 允许一条合法路径的最大跳数为 15
  • 不可达判定:当距离等于 16 时,即表示网络不可达
  • 技术评价:由于存在 15 跳的上限限制,RIP 只适用于小型自治系统(AS),无法应用于大规模的全球级网络。

3. RIP 路由表项格式

每个运行 RIP 的路由器都需要维护自己的路由表,其核心三元组表项格式为:

 N,  d,  X

  • 实质:路由器维护的从自身到其他每个目的网络的距离记录,在本质上就是其距离向量
QQ20260610-142648

二、 RIP 报文的协议层级与封装结构

1. 协议层级定位

  • ⚠️ 易错点敲黑板:虽然 RIP 是网络层路由协议,但在体系结构上 RIP 属于应用层协议

2. 下层传输依赖

  • RIP 在传输层使用 UDP 协议 传送数据。
  • 专用端口号:520

3. 数据逐层封装顺序

RIP 报文在流经网络栈时,包装逻辑如下:

RIP 报文 → UDP 数据报 (目的端口 520) → IP 数据报 (协议字段=17) → 数据链路层 MAC 帧

4. RIP 报文的两大基本类型

  • Request(请求)报文:用于路由器刚启动或查询时,请求邻居路由器发送其完整的路由表。
  • Response(响应)报文:又称路由刷新报文。用于主动响应请求,或在特定周期到达时自动向外分发。
    • ⚠️ 拆分限制规则:由于每个 Response 报文最多只能携带 25 个路由表项信息,如果路由器的路由表过大,就必须拆分成多个独立的 Response 报文进行分批传输。

三、 RIP 的信息交换机制 (Who / What / When)

运行 RIP 协议的路由器之间,其数据交互严格遵循以下三要素:

1. Who(和谁交换?)

  • 对等体限制仅和直接相邻的路由器交换信息。非相邻路由器之间不直接发生 RIP 通信。

2. What(交换什么?)

  • 内容总量:交换的是本路由器目前所知道的全部信息,即自己当前的整张路由表(即自身计算出的完整距离向量)。

3. When(何时交换?)

  • 定期交换:通常按固定的时间间隔(默认通常为 30 秒)自动向邻居发送一次 Response 报文。
  • ⚡ 触发更新(Triggered Update)机制
    • 引入目的:为了加快 RIP 的网络收敛速度。
    • 运行逻辑:一旦路由器自身发现网络拓扑发生了变化(如某个直连链路中断),它绝不等待 30 秒周期,而是立即向相邻路由器发射通告拓扑变化后的路由信息,从而在最大程度上防止路由环路的恶化。

RIP 工作过程与收敛机制

一、 RIP 路由表更新算法(核心机制)

当路由器收到相邻路由器发来的 RIP 报文时,严格按照以下两步执行更新算法(基于 Bellman-Ford 算法):

步骤一:对邻居发来的报文进行“预处理”

假设收到地址为 X 的相邻路由器发来的 RIP 报文,首先修改该报文中的所有项目:

  1. 将所有项目的“下一跳路由器”地址都改为 X
  2. 将所有项目的“距离(跳数)”字段的值全部加 1。

步骤二:对比原路由表,执行查表更新逻辑

针对修改后的各个报文项目,与本地原路由表逐一对比,按照以下 IF-ELSE 分支 决策:

  • 情况 A(发现新网络)
    • 条件:若本地原路由表中没有目的地网络 N
    • 动作直接将该项目添加到本地路由表中。
  • 情况 B(老路径更新,强行同步)
    • 条件:若本地原路由表中已有目的地网络 N,且原表项中的下一跳路由器地址刚好就是 X
    • 动作强制用收到的新项目替换原有的老项目(无论新项目的距离是变大还是变小,因为本地是通过 X 去往 N 的,必须以 X 的最新状态为准)。
  • 情况 C(全新路径对比,择优录用)
    • 条件:若本地原路由表中已有目的地网络 N,但原表项中的下一跳路由器地址不是 X(说明这是一条全新的备选路径)。
    • 动作:对比两者的距离值。
      • 若新项目中的距离 d 小于原路由表中的距离 执行更新,将下一跳改为 X,距离改为 d(发现了更近的路由)。
      • 若新项目中的距离 d 大于或等于原路由表中的距离 什么也不做(忽略该信息)。
QQ20260610-162648
QQ20260612-100456

额外补充:超时老化规则

  • 180 秒(RIP 的默认超时时间)内一直没有收到某个相邻路由器的更新路由表,则判定该相邻路由器出现故障。
  • 本地路由器会将该相邻路由器标记为不可达,即将其对应的所有路由表项的距离设置为 16(表示不可达)。

二、 RIP 协议的优缺点对比

优点(好消息传播得快)

  1. 实现简单、算法开销小。
  2. 若网络中出现了一条更短、更优的新路由,该好消息会在非常短的时间内迅速在自治系统内传遍所有路由器,网络收敛过程较快。

缺点(坏消息传播得慢)

  1. 限制了网络规模:它能使用的最大合法距离为 15,当距离达到 16 即代表不可达,因此无法用于大型网络。
  2. 开销随规模递增:路由器之间交换的是完整的路由表,网络规模越大,占用的链路开销和处理内存越大。
  3. 慢收敛现象:当网络出现故障、链路断开时,故障消息需要经过沿途路由器多次反复地信息交换和迭代才能最终收敛。

三、 深度解析:“坏消息传播得慢”与“计数到无穷”现象

结合课件中的 360 时刻拓扑故障演练,我们可以彻底复盘慢收敛(计数到无穷)的底层原因:

QQ20260612-131326

1. 正常收敛状态(0时刻)

  • 各路由器开机时,只知道自己到直接相连网络的距离为 1。
  • 经过若干次周期性(30秒)的邻居交换后,全网路由表项达到平稳,称为收敛。此时,R1 知道去往 Net2 需要通过 R2,距离为 2。

2. 故障发生与“相互欺骗”(360时刻)

  • 场景:去往 Net2 的链路突然损坏,或者路由器 R2 发生故障。
  • 环路产生的本质原因
    1. 当 R2 损坏后,R1 到 Net2 的直接路径其实已经断开。
    2. 然而,由于 RIP 的周期性异步更新,在故障坏消息还没来得及通告全网前,相邻的 R3 在 30 秒周期到来时向 R1 发送了自己的完整路由表。
    3. R3 的路由表中包含旧信息:〈Net2, 距离=3, 下一跳=R1/R4〉
    4. R1 收到 R3 的报文后,应用更新算法:下一跳设为 R3,距离加 1 变成 4。由于 R1 原本去往 Net2 的下一跳本是损坏的 R2,现在发现 R3 居然“能到”Net2,于是 R1 误信了 R3 的信息,将路由表更新为:通过 R3 去往 Net2,距离为 4。

3. 计数到无穷(Count to Infinity)的过程

  • 紧接着下一轮更新:R3 收到 R1 发来的新路由表(距离=4),根据“同一下一跳强行同步”或者算法叠加,R3 误以为通过 R1 去往 Net2 的距离变成了 4 + 1 = 5
  • 随后,R1 再次收到 R3 的更新,又将自己的距离加 1 修正为 6
  • 结论:R1、R3、R4 之间产生了路由环路,它们在彼此发来的报文中不断将距离“层层加码、互推自增”(从 4 递增到 5、6、7……)。

4. 破环机制

这种死循环会一直持续,直到多轮迭代后,Net2 的开销距离增加到 16 时,各路由器才依据 RIP 规定触发“16 = 不可达”的强行限制,丢弃环路路由。

这完美解释了为什么 RIP 协议在面对网络故障时,“坏消息传播得慢”。而将最大跳数粗暴地限制为 16,正是 RIP 为了防止路由环路无限扩散所采取的简单直接的兜底手段。

最短路径优先协议 OSPF(Open Shortest Path First)

一、 OSPF 协议概述与协议栈位置

1. 协议定位

  • ⚠️ 极高频考点(与 RIP 对比)OSPF 属于网络层协议,它不依赖传输层(TCP 或 UDP),而是直接使用 IP 协议提供的服务。
  • IP 首部关键标识:在封装 OSPF 分组的 IP 数据报首部中,协议(Protocol)字段值 = 89
  • 数据单元称谓:OSPF 的协议数据单元(PDU)常译为 “OSPF 分组”“OSPF 数据报”

2. 核心底层数据结构

  • 为了计算最佳路径,每一台 OSPF 路由器都需要在本地建立并维护整个网络的拓扑图
  • 数学模型:网络拓扑在底层被抽象为 带权有向图
  • 存储方式:在路由器内存中,通常使用 邻接表(Adjacency List) 结构来存储这一图结构。
  • 核心路由算法:在完整掌握全网拓扑的基础上,路由器调用 迪杰斯特拉(Dijkstra)算法 计算自身到各个目的网络的最短路径,进而生成最优转发表。
image-20260612144236262

二、 OSPF 工作流程与洪泛机制 (What / How / When)

OSPF 通过“链路状态(LS, Link-State)”的同步来让全网路由器达成拓扑共识,其交互逻辑表现为:

1. What:说什么?(链路状态通告)

路由器向外通告的是与自己直接相连的所有链路状态。具体包括:

  • 我是谁 本路由器的唯一标识(顶点)。
  • 我的邻居都有谁 与哪些路由器或网络直连。
  • 代价是多少 到达每个邻居的直接链路开销(带权有向边)。

2. How:怎么说?(洪泛法 Flooding)

  • 传播机制:使用 洪泛法。一传十、十传百,迅速扩散至整个自治系统(AS)内的所有 OSPF 路由器。
  • 转发防环规则
    1. 洪泛信息不可“回流”转发(即不能将收到的信息再倒手发回给发件接口)。
    2. 如果路由器收到了一条本地已经存在的、重复的链路状态信息,不可重复转发

3. When:什么时候说?(触发时机)

  • 动态触发:当路由器探查到自己身边的链路或节点发生变化时,再次立即洪泛最新的链路状态信息。
  • 优势判定:由于一旦有变动就立即精准洪泛,因此 OSPF 不存在“坏消息传播得慢” 的慢收敛现象。

4. 链路状态的版本控制:32位序号机制

  • 为了确保全网信息的一致性,每个链路状态都带有一个 32 位的序号
  • 版本判定规则序号越大,说明该链路状态越新(相当于信息的版本号)。这能保证旧的或延迟到达的干扰报文被路由器自动忽略。

三、 OSPF 的五种核心分组类型

OSPF 协议栈内部定义了 5 种不同的分组类型,用于维护邻居关系和同步拓扑数据库:

Type 值 英文全称 中文译名 核心功能与应用场景
1 Hello Packet 问候分组 用于发现、建立并周期性维护邻居路由器的可达性(“保持心跳”)。
2 Database Description Packet 数据库描述分组 (DD) 握手阶段使用。向邻居给出自己的链路状态数据库的摘要信息,供对方查漏补缺。
3 Link State Request Packet 链路状态请求分组 (LSR) 在收到 DD 分组后,向邻居请求发送本地缺失或已过期的某些链路状态的详细信息。
4 Link State Update Packet 链路状态更新分组 (LSU) 最为核心的分组。采用洪泛法向全网精准发送、同步具体的链路状态信息。
5 Link State Acknowledgment Packet 链路状态确认分组 (LSAck) 对收到的 LSU 更新分组进行显式的可靠确认,保证洪泛信息不丢失。

四、 OSPF 的其他高级重要特性

QQ20260612-142943

1. 灵活自定义链路“代价(Metric)”

  • OSPF 允许网络管理员针对不同的业务类型(如视频、音频或普通数据),为同一条链路赋予不同的代价值,从而计算出多条针对性路由。

  • 默认度量标准:主要基于带宽计算链路代价。

    $$\mathbf{\text{Metric(代价值)} = \frac{\text{参考带宽}}{\text{接口实际带宽}}}$$

    因特网默认参考带宽一般为 100 Mbps

  • ⚠️ 边权不对称规则:在实际应用中,一段链路的两个方向,其权值可能不同。另外,从【直连网络 路由器】方向的权值通常规定为 0

2. 支持等价多路径转发(ECMP, Equal-Cost Multi-Path)

  • 负载均衡:如果路由器计算出到达同一个目的网络有多条总代价值完全相等的最短路径,OSPF 可以同时启用这些路径,将通信量平均分配,从而实现高效的负载均衡

3. 强悍的鉴别功能(安全防范)

  • 安全机制:OSPF 分组支持在首部开启鉴别功能,从而保证只有在彼此信任的可信赖路由器之间才会交换链路状态信息。
  • 防范攻击场景
    • 恶意外部攻击:防止黑客潜入机房,用黑路由器伪造、洪泛虚假的拓扑信息。
    • 中间人篡改攻击:防止沿途被控制的病毒节点(如被污染的中间路由器)恶意篡改 OSPF 洪泛报文。

4. 完美支持 VLSM 与 CIDR

  • OSPF 彻底摆脱了分类 IP 的束缚,完美支持变长子网划分(VLSM)和无分类域间路由(CIDR),能够非常方便地处理通过二叉树机制灵活划分出来的各种大小不一的子网地址块。

OSPF的多区域划分

一、 OSPF 的“多区域(Area)”划分机制

image-20260612151147285

1. 为什么要划分区域?

  • 根本矛盾:当自治系统(AS)规模变得很大时,全网路由器数量激增,会导致两个致命问题:
    1. 洪泛流量过大:全网洪泛链路状态信息会吃满链路带宽。
    2. 计算开销过载:每个路由器的 LSDB(链路状态数据库)变得极大,运行 Dijkstra 算法计算最短路径时会严重消耗 CPU 资源。
  • 解决方案:将一个自治系统再划分为若干个更小的逻辑区域(Area)
  • 核心核心优化(考点):划分区域后,洪泛法(Flooding)交换链路状态信息的范围被严格局限在各个区域内部,而不是整个 AS。这样极大地减少了网络通信量,并使每个区域内部路由器的 LSDB 保持在一个较小的规模。

2. 区域的层级结构

在一个 OSPF 自治系统内部,区域划分为两个层级:

  • 主干区域(Backbone Area)
    • 标识符固定为 0(32位区域标识符 Area ID = 0.0.0.0)。
    • 核心作用:负责连通所有其他的非主干区域。系统中有且仅有一个主干区域。
  • 非主干区域
    • 标识符不为0(如 Area 1 可记为 0.0.0.1,Area 2 为 0.0.0.2 等)。
    • 连接规范:每一个非主干区域都至少有一台区域边界路由器(ABR)与主干区域直接相连。非主干区域之间通常不直接相连,它们的跨区域流量必须通过主干区域进行中转。

二、 OSPF 路由器的四种角色分类

根据路由器在多区域架构中所处的地理位置,OSPF 将路由器精确划分为以下几类(结合拓扑图示例):

路由器类型 英文简称 拓扑对应节点 核心核心职责与流量控制
区域内部路由器 (Internal Router) IR R1, R2, R5, R8, R9 也称非边界路由器。所有接口都属于同一个非主干区域,仅需知道本区域内部的局部网络拓扑。发往其他区域的流量一律盲投给本区域的 ABR 处理。
区域边界路由器 (Area Border Router) ABR R3, R4, R7 跨区域的桥梁。接口同时连接主干区域和非主干区域,负责流向/流出该区域的 IP 分组的路由转发与通告
主干路由器 (Backbone Router) BR R3, R4, R5, R6, R7 所有接口或至少一个接口属于主干区域(Area 0)的路由器。(注:ABR 同时也是主干路由器)
自治系统边界路由器 (AS Boundary Router) ASBR R6 处在 OSPF 自治系统的最外外沿,至少有一个接口连接了外部的其他自治系统(或其他路由协议区域),负责本自治系统与其他 AS 之间的域间 IP 分组路由转发

三、 OSPF 核心术语深度辨析(易混淆点)

课件中对三个高频概念进行了严谨的界定,复习时务必清晰:

  1. LSI(Link State Information,链路状态信息)
    • 性质:是一个广义的、抽象的概念
    • 含义:泛指所有用来描述链路状态、边开销、邻居关系的各种原始网络信息。
  2. LSA(Link-State Advertisement,链路状态通告)
    • 性质:是 OSPF 协议中显式定义的一种具体的数据结构(封装格式)
    • 含义:路由器实际产生的、用来在网络中进行洪泛传递的规范化承载单元。
  3. LSDB(Link-State Database,链路状态数据库)
    • 性质:是 LSA 的集合。
    • 含义:全网 LSA 汇聚在路由器内存中构建出的全网拓扑大地图(带权有向图的邻接表存储形式)。

四、 路由表的生成与事件驱动收敛特性

image-20260612151120461

1. 经典转折:从“完整路径”到“只存下一跳”

  • Dijkstra 算法的输出:以 R1 为例,运行迪杰斯特拉算法后,计算出的是到达全网各个网段的完整最优路径
    • 例如:到 Net4 的最优完整路径是:cost = 3,  R1 → R3 → R4 → Net4
  • 路由表的实际存储(核心考点):虽然算出了完整路径,但路由表中绝对不会存储完整路径,而只存储“下一跳(Next Hop)”和“当前距离”
    • 转换结果表项目的网络: Net4 | 下一跳: R3 | 距离: 3。这种设计极大地精简了路由表的体积,加快了查表转发速度。

2. 完美的事件驱动响应闭环

OSPF 属于事件驱动型协议,当网络平稳时,不发生无谓的信息交换。一旦网络发生风吹草动,将触发以下联动收敛机制:

网络拓扑发生变动(如某链路断开)

本地路由器探查到异动,立即产生并【洪泛】最新的 LSU(链路状态更新分组)

全网相邻路由器级联转发,迅速引起沿途各台路由器的【LSDB(拓扑图)】发生同步变更

各路由器在本地重新运行【Dijkstra(迪杰斯特拉)算法】

重新构造、刷新各自的【路由表】表项,网络达成全新收敛

OSPF 邻居关系建立与数据库(LSDB)同步

一、 三大核心阶段概述

QQ20260612-152346
  1. 阶段一:确定可达性
    • 核心目标:发现并确认双向邻居关系。
    • 依赖分组Type 1: Hello 分组(问候分组)。
  2. 阶段二:达到数据库的同步
    • 核心目标:互换本地拓扑地图的“简要目录”,找出彼此缺失的条目。
    • 依赖分组Type 2: DD 分组(数据库描述分组)。
  3. 阶段三:新情况下的同步(查漏补缺)
    • 核心目标:精准索取缺失的数据并进行可靠确认,最终让两端 LSDB 完全一致。
    • 依赖分组Type 3: LSRType 4: LSUType 5: LSAck

二、 五种分组流向与底层机制(深度解剖)

QQ20260612-152905

1. 确定可达性 —— Type 1: Hello 分组(问候分组)

  • 交互逻辑:当某时刻 R2 与 R1 建立物理连接,双方开始对传 Hello 分组。
  • 两个核心定时器(高频选择题考点)
    • Hello 周期(心跳间隔):各台路由器每隔 10 秒向其直接邻居发送一次 Hello 分组,以维持邻居关系的存活。
    • 死亡超时(Dead Timer):如果超过 40 秒没有收到邻居发来的 Hello 问候,路由器就会判定该邻居已经发生损坏或不可达。
  • 状态变更触发:初次建立连接时,Hello 分组会触发两端路由器的本地 LSA 序号发生 +1 修正(信息版本升级),准备向全网洪泛最新的链路状态。

2. 目录初对齐 —— Type 2: DD 分组(Database Description,数据库描述分组)

  • 交互逻辑:两台路由器的邻居关系建立好后,并不盲目发送庞大的完整数据库,而是首先向邻居互传 DD 分组。
  • 核心载荷:DD 分组内包含的是各自 LSDB 的摘要信息(即所有 LSA 的头部信息,不含具体载荷)
  • 通俗类比:相当于把各自手里的“拓扑地图”变成一份“精简目录索引”丢给对方。R1 和 R2 通过阅读对方的目录,就能立刻核对出哪些 LSA 对方有而自己没有,或者哪些 LSA 对方的版本比自己更新。

3. 精准查漏补缺 —— Type 3 ~ Type 5 分组联动闭环

当两台路由器比对完 DD 分组的“目录索引”后,将触发以下三步高可靠响应闭环:

  • 核心机制:对比 DD 目录后,如果 R1 发现 R2 拥有自己缺失的、或者版本更新的 LSA 项,R1 就会产生并向 R2 单播发射 LSR 分组。
  • 形象写照(源自课件趣味表情包):“好朋友作业借我抄抄”——在分组里显式列出所有自己需要抄录的 LSA 编号。
  • 核心机制:这是 OSPF 协议中最核心、数据量最大的实质性分组。R2 收到 R1 的 LSR 请求后,将对方索要的、包含完整底层信息(邻居是谁、代价多少)的 LSA 完整数据打包嵌入 LSU 分组中,单播回复给 R1。
  • 注:在真实的现网环境中,一旦任何一处链路发生变动,路由器也会主动产生 LSU 向全网进行事件驱动型洪泛通告。
  • 核心机制:R1 成功接收到 R2 发来的 LSU 完整内容后,必须回传一个 LSAck 分组进行显式交代。
  • 载荷内容:LSAck 中携带确认收到的 LSA 头部信息(“已确认收到”)。
  • 技术价值:由于 IP 协议本身是不可靠的,OSPF 作为直接运行在网络层上的协议,通过 LSU ↔︎ LSAck 机制在网络层实现了数据的 100% 可靠传输,确保同步过程中不会因为丢包而导致拓扑图产生偏差。

边界网关协议 BGP(Border Gateway Protocol)

一、 BGP 协议的定义与四大核心特点

1. 根本定位

BGP 属于 外部网关协议(EGP),专门用于在不同的自治系统(AS)之间高效率地交换路由信息并实现跨系统的路由选择。

2. 四大核心特点(高频选择题考点)

  1. 追求“较好路由”,而非“最佳路由”:因特网规模太大,跨越全球不同运营商,寻找绝对的数学“最短路径”是不现实且没有意义的。
  2. 基于“策略”进行路由选择:AS 之间的选路必须通盘考虑政治、安全、控制或经济等非技术因素(例如:某国流量绝不从竞争对手国家的 AS 中转)。
  3. 采用路径向量(Path-Vector)路由算法
    • 路由器在向邻居通告 BGP 路由信息时,不仅告诉对方目的地是谁,还会显式告知到达该目的地需要经过的完整自治系统路径(即 ASN 序列)
    • 技术优势:拿到完整的 AS 路径后,路由器如果发现自己的 AS 编号已经在路径列表里,就能瞬间识别并斩断路由环路
  4. 协议层级定位:⚠️ 极易错点!BGP 属于应用层协议,其底层传输依赖 TCP 协议 提供可靠连接,专用的 TCP 端口号 = 179

二、 BGP 邻居关系与会话分类 (eBGP vs iBGP)

1. BGP 邻居(BGP 对等方 / BGP Peers)

运行 BGP 协议的通信双方互称为 BGP 对等方或邻居。两台边界路由器在物理链路连通后,必须首先建立底层 TCP 三次握手连接,随后才能在 TCP 管道之上交换 BGP 报文、确立 BGP 会话(Session)

QQ20260614-130207

2. 两类 BGP 会话(看图复习,重点区分位置)

  • eBGP 会话(External BGP):处于两个不同 AS 边界且直接相连的路由器之间保持的会话(例如图中的 R6 ↔︎ R4R2 ↔︎ R1)。
  • iBGP 会话(Internal BGP):处于同一个 AS 内部的所有运行 BGP 的路由器之间保持的会话(例如图中的 R2, R3, R4, R5 两两之间)。

3. ⚠️ 核心水平分割规则与全连通(Full Mesh)约束

  • iBGP 核心水平分割规则:从一个 iBGP 邻居学到的 BGP 路由信息,绝对不能再通过 iBGP 传播给局域网内部的其他 iBGP 邻居。引入此规则是为了死死卡住 AS 内部的路由环路扩散。
  • 带来的 Full Mesh 约束:由于不能在内部“倒手传话”,为了让 AS 内部所有路由器都能同步获知外部的路由,规定同一个 AS 内部的所有 BGP 路由器之间必须建立两两全连通(Full Mesh)的 iBGP 会话。若有 N 个内部 BGP 路由器,则在网络内部共需维系 $\frac{N \times (N-1)}{2}$ 条高成本的 iBGP 会话。

三、 BGP 路由信息结构与跨系统传播原理

1. BGP 路由信息组合

一条完整的 BGP 路由条目结构表现为:【CIDR 网络前缀, BGP 属性】

2. 两大常考核心 BGP 属性

  • AS-PATH(自治系统路径):记录了去往目的网段沿途经过的所有自治系统编号序列。
  • NEXT-HOP(下一跳地址,改写规则是核心大题考点)
    • 在走 eBGP 跨系统发送时:路由器会将“下一跳”改写为自己接口的 IP 地址
    • 在走 iBGP 内网同步时:⚠️ 默认绝不修改“下一跳”!(谢希仁教材与 RFC 标准)。如图中 R2 从外网学到网段 X 传给内网的 R4 时,下一跳仍保持为外网的 R1。这是为了让内网路由器明确知道真正的流量出口是谁。(注:在实际工业界或某些特定真题如2024真题47题中,也可以通过特殊配置强行修改为发送方自己的内网IP)
QQ20260614-130526

四、 BGP 决策算法:四步高级选路策略 (How)

当路由器通过 BGP 从外部不同的多条路径同时学到了去往同一个外部目的网络 X 的多条 AS-PATH 时,它会启动以下四步优先级过滤算法来做决定:

步一:选择本地偏好值(Local Preference)最高的路线

  • 机制:由本地 AS 的网络管理员手工进行全局调配,相当于给不同出口链路上锁、赋予不同的“初始优先级”。通常会结合各链路的运营带宽成本和安全系数来动态评配。

步二:选择 AS 跳数(AS-PATH 长度)最少的路线

  • ⚠️ 核心敲黑板AS 跳数最少,不等于路由器跳数最少。它只是说明穿过的国家/运营商区域最少。如果此项依然打平,则向下交由内网算法仲裁。

3. 步三:触发“热土豆路由选择算法(Hot Potato Routing)”

  • 底层理念“烫手的山芋,要尽快脱手”
  • 运行逻辑:如果有多条外部路线的 AS 跳数完全相同,那么路由器会查阅自己本地内部的网关协议(IGP,如 RIP 或 OSPF),计算出本机到达各个出口边界路由器所需的内网最小代价(Cost)
  • 数据包将从本地内网代价最小的那个边界出口被立刻驱逐出去。
    • RIP 协议下,代价看的是内网路由器跳数;OSPF 协议下,代价看的是内部路径总长度。
QQ20260614-133833

步四:选择 BGP 标识符(BGP ID)最小的路由

  • 兜底策略:如果前三步全部打平,为了防止流量产生动荡,执行最终兜底。
  • BGP ID 规范:每台 BGP 路由器都有一个全球唯一的 32 位标识符(通常选取设备所有接口中数值最大的 IP 地址充当)。算法将盲选 BGP ID 最小的那台邻居路由器发来的路由(“听老员工的”)。

五、 BGP 的四种核心报文类型表

BGP 报文直接作为 TCP 的数据载荷进行高可靠传输,其定义的 4 种基础报文功能如下:

报文名称 英文全称 核心功能解析 触发与发送时机
① 打开报文 Open 用于与相邻的另一个 BGP 对等方进行初始化握手,协商参数并正式建立 BGP 会话 底层 TCP 连接成功建立后,BGP 吐出的第一个报文。
② 更新报文 Update 最为核心的报文。用于向全网通告某条可达路由的详细属性,或声明撤销掉多条已经失效的死路由。 动态触发:一旦发现新的路由线索,或者既有路由属性发生变化时立即发射。
③ 保活报文 Keepalive 纯净的心跳跳动报文。用于向邻居周期性证明自己会话存活,维系 BGP 邻居状态不崩溃。 周期性自动发送(通常为 60秒 一次)。若超出 3 倍周期(180秒)未收到对方的保活,则断开会话并宣告邻居死亡。
④ 通知报文 Notification 异常绝杀报文。当检测到网络或报文首部发生严重差错时,向对方发送错误编码,并立即强行关闭 BGP 会话和底层的 TCP 连接。 处于被动触发状态:检测到 BGP 运行错误时立即发射(如报文首部参数校验出错)。

IP多播

一、 IP 多播的基本概念与核心优势

1. 什么是 IP 多播?

  • 定义:IP 多播(Multicast,曾译为 IP 组播)诞生于1980年代末,是一种典型的“一对多”通信技术。源主机只需发送一个 IP 多播分组,该分组在网络中传输时,由支持多播功能的路由器进行“按需复制”并“多口转发”,最终精准交付给一组特定的接收端主机。
  • 经典应用:网络视频直播、视频会议、IPTV(网络电视)等。

2. ⚡ 深度对比:单播 (Unicast) vs 多播 (Multicast)

假设视频服务器需要向内网的 90 个视频接收端(群成员)同时推送一段流媒体数据:

  • (a) 传统单播模式
    • 机制:服务器必须串行或并行地向这 90 台主机独立发送 90 次单播分组
    • 缺陷:主干链路(如主干路由器 R1 之前的链路)会被瞬间塞满 90 个一模一样的数据包拷贝,极度浪费网络带宽,且服务器的 CPU 和网卡 I/O 压力巨大。
  • (b) 升级多播模式
    • 机制:服务器端仅需发送 1 次多播分组,该包的目的 IP 指向一个公共的“多播组地址”。当数据包到达关键分叉路径上的多播路由器(如 R1)时,路由器发现其下方的三个分支接口内都存在该多播组的成员,于是 R1 在本地按需复制出 3 个副本,分别向三个分支转发。
    • 优势最大程度节省主干链路带宽,主干电路上永远只有一个数据包在流动,且服务器端实现了解耦与减负。
QQ20260614-144743

💡 考研名师趣味类比

  • IP 单播 微信“单独私聊”发送消息(单播地址 = 个人微信号)。
  • IP 多播 微信“在微信群内”发消息,群成员都能收到(多播地址 = 微信群号)。

二、 IP 多播地址与三大底层特性(高频考点)

1. 多播 IP 地址规范

  • 对应网段:属于 IPv4 地址方案中的 D 类地址

  • 二进制前缀:最高 4 位固定为 1110

  • 十进制合法范围

    224.0.0.0239.255.255.255

  • 成员特性:一个多播地址唯一对应一个特定的“多播组”,一个组内可以动态包含零台、一台或多台主机。

2. IP 多播的三大硬性特点(敲黑板划重点)

  1. 尽最大努力交付(Best-Effort):多播 IP 数据报在网络层同样提供的是无连接、不可靠的交付服务,协议本身不提供可靠交付保证(丢包、乱序由上层应用自行处理)。
  2. ❌ 绝不能作为源地址(核心死穴):多播地址只能作为 IP 首部中的“目的 IP 地址”。在网络中传输的任何一个合法数据包,其“源 IP 地址”必须是一个确切的、全球唯一的单播 IP 地址,绝对不能是 D 类多播地址。
  3. 🤫 杜绝触发 ICMP 差错报告:如果多播数据报在传输途中发生任何差错(如 TTL 减为0、找不到路由等),沿途路由器将其丢弃时绝对不产生、不回传 ICMP 差错报告报文。这是为了防止大量多播路由器同时向源端回传差错报文,从而在内网引发毁灭性的“ICMP 广播风暴”。

三、 维系 IP 多播运转的两大核心协议

要实现高效的全球级或局域网级多播,多播路由器必须同时运行并维护以下两种运行在网络层的辅助协议:

1. IGMP 协议(Internet Group Management Protocol,网际组管理协议)

  • 层级与标识:属于 网络层协议。在封装 IGMP 报文的 IP 数据报首部中,协议(Protocol)字段值 = 2
  • 核心职责:专门用于管理和维护本地局域网(LAN)内部的主机与直接相邻的多播路由器之间的成员映射关系。
  • 通俗功能:管辖主机“如何加群”与“如何退群”。多播路由器通过周期性发射 IGMP 查询报文,获知本局域网内目前还有没有该“QQ群”的成员,从而决定要不要把外网接收到的该群的多播流放行导入到本地局域网内。

2. 多播路由选择协议(Multicast Routing Protocol)

  • 核心职责:专门用于在多播路由器与多播路由器之间进行拓扑和成员信息的交换。
  • 核心功能:管辖“多播数据包在外网大方向上该怎么走”。通过多播路由协议,全网路由器共同配合,建立一棵无环的、以源端为根或以共享点为中心的“多播转发树”,确保外网流量能够精准“多口转发”投递到每个有群成员的接口,同时避开没有群成员的空闲分支,防止流量泛滥。

移动 IP 技术(Mobile IP)

一、 移动 IP 技术要解决的核心问题

  • 传统 IP 的局限性:在传统 TCP/IP 网络中,主机的 IP 地址与其接入的物理子网(网络前缀)强绑定。一旦移动设备在跨区域移动时切换了子网,其 IP 地址就必须动态改变。
  • 带来的网络灾难:如果移动设备的 IP 地址突然改变,上层正在进行的 TCP 长连接(如正在传输大文件、视频通话)会瞬间发生断连并全部强制中断
  • 移动 IP 的使命:确保移动节点(如手机、笔记本)在跨越不同子网漫游移动时,依然能够保持其原有的永久 IP 地址完全不变。从而让上层网络应用(如 TCP 会话)对物理位置的变化完全透明,实现无缝平滑过渡。

二、 移动 IP 的核心基础概念与物理实体

根据课件,移动 IP 体系内包含以下 6 个不可混淆的核心术语:

核心术语(中英文) 实体角色与具体含义
移动节点 / 移动站 (Mobile Node, MN) 具有永久 IP 地址、在各个不同物理子网之间跨区移动的主机(即你的手机或移动设备)。
永久地址 / 本地地址 (Home Address) 移动节点最初在老家获得的、全球唯一的、长久不变的 IP 地址。无论移动站漫游到天涯海角,上层通信始终只使用这个永久地址
归属网络 / 本地网络 (Home Network) 移动节点最初所属的、与其永久地址网络前缀完全匹配的原始物理局域网。
归属代理 / 本地代理 (Home Agent, HA) 连接在归属网络上的核心路由器。当移动节点跑到外地时,归属代理在老家充当数据管家,负责代收发往该移动节点的所有流量
被访网络 / 外地网络 (Foreign Network) 移动节点当前漫游、接入移入的非原始归属的陌生外地子网。
外地代理 (Foreign Agent, FA) 连接在当前被访网络上的路由器。负责接应并维系漫游过来的移动节点,并作为隧道的终点接收数据。
转交地址 (Care-of Address, COA) 移动节点漫游到外地网络时,被外部动态分配的临时 IP 地址

⚠️ 工业界与考试三大硬性注意点(划重点)

  1. 转交地址的通信局限:转交地址纯粹是供移动站、归属代理及外地代理之间内部通信套娃使用的。外部的普通通信者(如通信者 B)根本不需要知道、也感知不到转交地址的存在。
  2. 转交地址的共享特性一个临时转交地址可以被同一个外地网络下的多台移动站共同复用。外地代理(FA)在向本地派发时,可以直接根据各个移动站独一无二的 MAC 地址进行精准链路层投递。
  3. 网卡状态判定:移动站处于本地网络时,其通信模式与传统的普通单播通信完全一致。

三、 移动 IP 完整通信全流程(核心大题考点)

当移动站 A(永久 IP 地址为归属网络段)漫游离开老家,进入通信者 B 所在的或外部的外地网络进行通信时,完整历经以下四个阶段:

1. 漫游与登记阶段(MN 到达外地网络)

  • ① 登记暂住:移动站 A 接入外地网络后,首先向当前的外地代理(FA)进行登记,获得当前物理位置关联的临时转交地址(COA)。
  • ② 向老家报备:外地代理(FA)代为向 A 老家的归属代理(HA)进行登记报备,告知 HA:“你家 A 目前在我这,它的临时转交地址是 XX”。
  • ③ 隧道建立:老家的归属代理(HA)接收并批准登记,在 HA 与该转交地址之间成功建立一条通往外地的虚拟隧道(Tunnel)
image-20260614171241778

2. 接收数据阶段:移动站在外地网络【接收】IP 分组(IP-in-IP 隧道套娃)

这是移动 IP 最具特色的“三角路由(Triangular Routing)”过程:

  • 步骤一(B 正常寄出):通信者 B 想要给移动站 A 发送数据。由于 B 仅知道 A 的永久地址,B 构造原始 IP 分组:
    • 【源 IP = B 的 IP 地址, 目的 IP = A 的永久地址】
  • 步骤二(HA 拦截并套娃):该数据包被常规路由派发到 A 的老家网络,随即被老家的守护管家——归属代理(HA)强行截获。HA 读取后发现 A 在外地,于是 HA 将整个原始 IP 分组作为数据载荷,在外面套上一个全新的外层 IP 首部(称为 IP-in-IP 隧道封装):
    • 【外层源 IP = 归属代理(HA)的 IP, 外层目的 IP = A 当前的临时转交地址(COA)】
  • 步骤三(隧道传输与拆封):这个套娃分组穿过广域网,沿着隧道高效率投递到终点——外地代理(FA)。外地代理将外层首部无情剥离(解封装),暴露出里面完整的原始 IP 分组。
  • 步骤四(落地交付):外地代理(FA)利用数据链路层封装,将原始 IP 分组直接精准投递到移动站 A 的网卡上。A 成功接收。

3. 发送数据阶段:移动站在外地网络【发送】IP 分组

  • 核心规则(绝不绕回老家!):身处外地的移动站 A 如果想要回复或向通信者 B 主动发射数据,其过程极其简单。A 直接在本地构造并输出最纯粹的原始 IP 分组:
    • 【源 IP = A 的永久地址(哪怕在外地,自报家门也必须使用老家永久IP), 目的 IP = B 的 IP 地址】
  • 直接路由:该数据包直接交给当前的外地代理(FA),外地代理将其直接推入普通公共 Internet。数据包不需要绕回老家的归属代理,而是沿着普通的网络最短路径,以最快速度直接单播送达通信者 B。

4. 回归故里阶段(MN 回到归属网络)

  • 登记注销:当移动站 A 漫游结束,物理上重新切回自己的老家(归属网络)时,会立即向自己的归属代理(HA)发起登记注销
  • 通道恢复:HA 收到注销通知后,当即依法撤销之前的转交地址映射关系,并关闭虚拟传输隧道。至此,移动站 A 的所有通信行为完美且自动地恢复到最传统、最普普通通的经典单播 TCP/IP 通信模式。

传输层

核心概述

一、 传输层核心功能与基本概念

1. 实现“端到端”(进程到进程)的通信

  • 网络层 vs 传输层
    • 网络层:实现“主机到主机”的通信。信息记录在 IP 数据报的首部,指明源 IP 地址目的 IP 地址
    • 传输层:实现“端到端”(进程到进程)的通信。信息记录在 TCP(或 UDP)报文段的首部,指明源端口目的端口。传输层根据端口号来区分数据来自或去往哪一个特定的进程。

2. 复用与分用

  • 复用(从上到下):在发送数据的时候,同一台主机上的多个进程可以使用同一个传输层协议。
  • 分用(从下到上):在接收数据的时候,传输层可以根据端口号把数据正确交付到对应的目的进程。

3. 差错检测

  • TCP:检测出差错后丢弃数据,并通知发送方重传
  • UDP:检测出错误后直接丢弃数据,但不通知发送方

二、 端口与端口号

1. 端口的作用与独立性

  • 通过“端口号”标识本主机的一个特定进程。
  • 相互独立性
    • 每台主机的端口号是相互独立的。
    • TCP 和 UDP 两种协议的端口号也是相互独立的。

2. 套接字(Socket)

  • 公式

    套接字 (Socket) = {IP地址 : 端口号}

  • 作用:唯一地标识网络中的一台主机上的一个应用进程(IP地址 + 端口号→ 指向网络中一台主机上的一个特定的进程)。

  • 分类:分为 TCP 套接字UDP 套接字

  • 进程通信的三要素:当两个进程之间想要通信时,必须指明:

    1. 使用哪种传输层协议(TCP 或 UDP);
    2. 本进程绑定的端口号;
    3. 对方的 IP 地址和端口号。
QQ20260614-172937

3. 端口号的分类

  • 服务器使用的端口号
    • 熟知端口号(0~1023):通常只能用于被熟知的重要应用程序(系统预留)。
    • 登记端口号(1024~49151)
  • 客户端使用的端口号
    • 短暂端口号(49152~65535):进程运行时动态分配,运行结束即释放。

三、 传输层提供的两种传输服务对比(TCP vs UDP)

传输层向应用层提供两种截然不同的端到端传输服务:

1. 有连接 vs 无连接

  • 面向连接的传输(如 TCP)
    • 机制:传输前先“打招呼”(建立连接),确认对方已经准备好接收数据;数据传输结束时,也要告知对方已结束(释放连接)。
    • 阶段:包含 建立连接 数据传输 释放连接 三个完整阶段。
  • 无连接的传输(如 UDP)
    • 机制:不打招呼,直接把数据传给对方(直接进入数据传输阶段)。

2. 可靠 vs 不可靠

  • 可靠的传输(如 TCP)
    • 机制:接收方使用“确认机制”(如收到数据后向发送方反馈“①收到、②收到”),让发送方明确知道哪些数据已被正确接收。
    • 优缺点确保数据正确和完整,但系统开销大、实时性较差。
  • 不可靠的传输(如 UDP)
    • 机制:接收方无论有没有收到数据、数据是否正确,都不给发送方任何反馈
    • 优缺点:数据可能会出错或丢失,但速度快、开销小

UDP 协议

一、 UDP 与 TCP 核心特性深度对比

传输层的两大核心协议在报文处理、连接机制和传输可靠性上有着本质的区别:

特性维度 UDP 协议 TCP 协议
工作机制 面向报文:不拆分、不重装。应用层交下来的报文,UDP 仅添加首部后就向下交付。 面向字节流:支持报文的自动拆分与重装,可以传输极长的应用层报文。
首部开销 首部很小,固定为 8 字节 (8B) 首部较大,占 20~60 字节 (20~60B)
连接状态 无连接。不需要提前建立连接,直接发送数据。 面向连接。通信双方在传输层必须先通过“三次握手”建立连接。
可靠性 不可靠。不保证交付,不提供拥塞控制(可靠性可由应用层自行实现)。 可靠。通过确认、重传及拥塞控制机制确保数据无差错、不丢失、按序到达。
通信支持 支持一对一、一对多(支持单播、广播和多播/组播)。 仅支持一对一的单播传输。

⚠️ UDP 核心注意点:由于 UDP 不支持报文的自动拆分与重装,因此应用层交付的报文长度绝对不能超过 UDP 协议的上限,否则会在网络层引发复杂的 IP 分片。

QQ20260614-194836

二、 UDP 用户数据报格式

UDP 用户数据报由 首部(Fixed 8 Bytes)数据部分 组成。

1. 首部字段构成(共 8B,分为 4 个字段,每个字段占 2B / 16位):

  1. 16位源端口号(Source Port):发送方进程的端口。如果在某些场景下不需要对方回复,可直接置为全 0
  2. 16位目的端口号(Destination Port):接收方进程的端口,此项为必填
  3. 16位UDP长度(Length):指明整个 UDP 用户数据报的长度(包含首部 + 数据部分),单位为字节(Byte)。其理论最大值为 65535 B
  4. 16位UDP检验和(Checksum):由发送方传输层计算并填入,接收方用来检测传输过程中是否发生比特差错。如果不需要检验,可直接填入全 0

2. 数据部分(Data):

  • 即应用层交付下来的一个完整的应用层报文
QQ20260614-195625

三、 UDP 数据封装示例分析

以图片中发送一个 96 B 的应用层报文为例,其在各层的封装与字段变化如下:

  1. 应用层:生成原始报文,长度为 96 B
  2. 传输层(UDP):添加固定 8 B 的 UDP 首部,封装成 UDP 数据报
    • 此时,首部中的 UDP 长度字段 = 8B (首部) + 96B (数据) = 104
  3. 网络层(IP):将 104B 的 UDP 数据报作为数据部分,前方加上 20 ∼ 60 BIPv4 首部,封装成 IP 数据报 向外发送。

四、 UDP 差错检验机制:二进制反码求和(Checksum)

为了提高检测的准确性,UDP 引入了一种特有的检验方法,在计算检验和时引入了“伪首部”。

1. 什么是伪首部(Pseudo Header)?

  • 目的:仅用于计算检验和,既不向下交付给网络层,也不向上递交给应用层。计算前临时添加,计算完后立即去掉
  • 大小与结构(共 12 字节)
    • 源 IP 地址(4 字节)
    • 目的 IP 地址(4 字节)
    • 固定 0(1 字节)
    • 协议类型(1 字节,UDP 的协议号固定为 17
    • UDP 长度(2 字节,与 UDP 首部中的长度值完全相同)

2. 差错检验核心算法(二进制反码求和 + 回卷)

  • 基本原理:将需要检验的数据以 16位(2字节)为一组 进行二进制加法。
  • 最高位进位回卷:若最高位(第16位)相加后产生了进位,必须将该进位“回卷”加到最低位。
QQ20260614-200707

3. 发送方与接收方的完整处理步骤

🔹 发送方的传输层逻辑:

  1. 添加伪首部:在 UDP 用户数据报前端临时拼接 12B 的伪首部。
  2. 分组加法:把【伪首部 + UDP首部 + 数据部分】整体以 16位 为一组,进行二进制加法。(注:若数据部分字节数为奇数,需在末尾补一个全0字节,该字节同样不发送)。此时首部中的“检验和”字段先填入全 0。
  3. 求反码:将最终的加法结果逐位取反,得到 16 位的“检验和”,将其填入 UDP 首部的检验和字段。
  4. 去掉伪首部:删除伪首部,将标准的 UDP 数据报交付给网络层。
QQ20260614-201049

🔹 接收方的传输层逻辑:

  1. 接收并补齐:收到网络层递交的 UDP 数据报后,在前端添加完全相同的伪首部
  2. 全员求和:把【伪首部 + 含有检验和的UDP首部 + 数据部分】同样以 16位 为一组,进行二进制加法(同样支持最高位进位回卷)。
  3. 结果判定
    • 如果没有发生比特错误:最终的二进制加法结果必然为全 1(即 1111 1111 1111 1111)。接收方顺利接收该报文,并根据目的端口号交付给对应应用层进程。
    • 如果结果不是全 1:说明传输过程中发生差错,直接丢弃该 UDP 数据报。

TCP 协议核心特性与首部格式

一、 TCP 连接生命周期管理(概述)

TCP 是面向连接的协议,其生命周期包含:建立连接(3次握手) 数据传输(全双工) 释放连接(4次挥手)

QQ20260614-202431

1. 建立连接(3次握手)

  • 角色定义客户端是主动发起连接的一方;服务器是被动等待连接的一方。
  • 握手过程:通过 3次握手 建立连接,在网络中对应 3个 TCP 报文段
  • 通信状态:一旦连接建立成功,进程 A 和 B 即可进入全双工通信(双向传输 TCP 报文段)。

2. 释放连接(2 + 2 = 4次挥手)

由于 TCP 是全双工的,断开连接时需要双向分别释放:

  • 挥手 ① & ②:进程 A 没有数据要发送了,发起 2 次挥手断开 A B 的单向连接。
  • 半关闭状态:此时连接进入单工通信状态,进程 A 只能接收数据,只有进程 B 可以继续给进程 A 发送数据
  • 挥手 ③ & ④:进程 B 也没有数据要发送后,发起后 2 次挥手断开 B A 的单向连接。至此结束通信。

二、 TCP 面向字节流(Byte-Stream Oriented)的特性

QQ20260614-203425

1. 核心概念

  • 传输机制:无论应用层一次交付多少个、多大的报文,在 TCP 看来都是一连串无结构的字节流
  • 单次连接传多个报文:建立一次 TCP 连接可以双向传输多个应用层报文。
  • 报文的拆分与重组:TCP 会根据对方协商的 MSS(最大段长) 自动拆分和打包数据。

2. 字节流拼接与拆分示例

假设进程 1 要连续发送报文 Y(1600B)和报文 Z(1800B),双方协商的 MSS = 1000 B

  • TCP 并不强制要求每个 TCP 段都满载,只要不超过 MSS 即可。
  • 打包结果
    • TCP 段 1:TCP 首部 + 报文 Y 的前 1000 B
    • TCP 段 2:TCP 首部 + 报文 Y 剩余的 600 B + 报文 Z 的前 400 B不同应用层报文的数据可以在同一个 TCP 段中拼装
    • TCP 段 3:TCP 首部 + 报文 Z 的中间 1000 B
    • TCP 段 4:TCP 首部 + 报文 Z 剩余的 400 B
  • 接收端处理:这 4 个 TCP 段在网络中可能会乱序到达,但接收方的 TCP 协议栈会自动按序号重新组装排序,最终按序递交给应用层进程 2。

三、 TCP 报文段首部格式详解

TCP 报文段由 TCP 首部TCP 数据部分 组成。其固定首部为 20 字节 (20B),通过选项字段可扩展,最长可达 60 字节 (60B)

QQ20260615-084153

1. 端口号(各占 2B / 16位)

  • 源端口目的端口:标识发送方和接收方的应用层进程。

2. 序号与确认号(各占 4B / 32位)—— 拆包与重组的核心

  • 序号(seq):用于标记本报文段所发送的数据部分第一个字节在原始字节流中的位置。
    • 注意:起始序号是由发送方随机设置的,不一定从 0 开始
  • 确认号(ack 或 ack_seq):用于期望收到对方下一个报文段的第一个数据字节的序号。表示在该确认号之前的所有字节都已正确收到

3. 数据偏移与保留字段

  • 数据偏移(4位):实际上表示 TCP 首部的长度
    • 计算单位:以 4 B 为单位。其 4 位二进制取值范围是 0 ∼ 15
    • 最大长度15 × 4 B = 60 B(扣除 20B 固定首部,选项部分最多 40B)。
    • 注意:TCP 首部中不会专门记录 TCP 数据部分的长度,该长度是通过 IP 首部中的总长度信息计算出来的。
  • 保留(6位):目前暂未使用,通常全部置为 0。

4. 核心标志位(6位,每位占 1 bit)

  • ACK:确认有效位。ACK=1 时确认号(ack_seq)才有效;ACK=0 时确认号无效。
    • 注意:在 TCP 全生命周期中,只有连接建立的“握手①”报文段 ACK=0,其余所有 TCP 报文段的 ACK 必须置为 1。
  • SYN:同步位。SYN=1 表示这是一个连接请求或连接接受报文
    • 注意只有“握手①”和“握手②”的 SYN=1,其他传输阶段的 SYN 均为 0。
  • FIN:终止位。FIN=1 表示此报文段发送方的数据已发送完毕,要求释放传输连接。
    • 注意只有挥手阶段的“挥手①”和“挥手③”的 FIN=1
  • RST:复位位。RST=1 时表示 TCP 连接中出现严重差错(如主机崩溃),必须强制释放连接重新建立。也可用于拒绝非法报文段或黑客恶意攻击。
  • URG:紧急位。URG=1 时紧急指针字段有效,告诉系统此报文段中有紧急数据,应插队优先发送。
  • PSH:推送位。PSH=1 时要求接收方尽快将数据交付给应用层进程,而不要等到缓存填满再向上交付(常用于交互式通信)。

5. 窗口与功能字段

  • 窗口(16位,简记为 rwnd 或 rcvwnd):表示接收方的接收窗口大小
    • 含义:指明从本报文段首部中的 ack_seq 算起,接收方目前还能接收多少个字节的数据
    • 作用:是实现流量控制的关键字段。
  • 紧急指针(16位):当 URG = 1 时有效,指出本报文段中紧急数据的具体字节数(紧急数据排在数据部分的最前面)。
  • 检验和(16位)
    • 原理与 UDP 雷同,计算前同样需要临时添加 12B 的伪首部
    • 区别:只需将伪首部中的协议字段由 17(UDP)改为 6(TCP),并将原来的 UDP 长度字段改为 TCP 长度

6. 选项(选项长度可变,占 0~40B)

  • 填充:为了确保整个 TCP 首部是 4 B 的整数倍,若选项长度不足,后面需加全 0 字节填充凑足。
  • MSS(最大段长)协商:在建立连接时(即 握手①、握手② 的选项中),双方会协商接下来的传输中一个 TCP 报文段最多能携带的数据字节数(不包括首部)。通常 MSS 不会设置过大,以避免在 IP 层被分片。

TCP 三次握手与四次挥手

一、 核心控制位(Flags)的绝对规律

在 TCP 全生命周期的报文段中,标志位满足以下“只有……”的确定性排他规律(选择题高频考点):

  • ACK(确认位)只有握手①的 ACK = 0,其余全生命周期的所有 TCP 报文段均为 ACK = 1
  • SYN(同步位)只有握手①和握手②的 SYN = 1,其余所有 TCP 报文段均为 SYN = 0
  • FIN(终止位)只有挥手①和挥手③的 FIN = 1,其余所有 TCP 报文段均为 FIN = 0

二、 TCP 三次握手核心机制

QQ20260615-091440

1. 详细交互过程与序号变化示例

假设客户端初始序号 seq = 666,服务器初始序号 seq = 50

  • 握手 ①(Client Server)
    • SYN=1, ACK=0, FIN=0seq = 666。此时确认号 ack 无效。
  • 握手 ②(Server Client)
    • SYN=1, ACK=1, FIN=0seq = 50ack = 667(即 666 + 1)。
  • 握手 ③(Client Server)
    • SYN=0, ACK=1, FIN=0seq = 667ack = 51(即 50 + 1)。

2. 🚨 核心考点:携带数据与消耗序号规则

QQ20260615-092120
  1. 握手 ① 和 握手 ②绝对不能携带数据(只包含 TCP 首部,数据部分为 0B)。但是,它们固定消耗 1 个序号。因此下一阶段的 ack 必须在收到的 seq 基础上 +1
  2. 握手 ③可以携带数据,也可以不携带数据
    • 如果不携带数据不消耗序号。下一个由客户端发出的 TCP 报文段的序号仍然是 seq = 667
    • 如果携带数据(例如携带了 100B 数据):下一个由客户端发出的 TCP 报文段的序号将变为 seq = 667 + 100 = 767

3. 握手阶段耗时分析

  • 从客户端发出握手①,到客户端进程可以发送数据,至少需要多远?
    • 答:1 RTT。因为客户端发出握手①,经过 0.5 RTT 到达服务器,服务器立刻发出握手②,再经过 0.5 RTT 到达客户端。此时客户端进入 ESTABLISHED 状态,数据可以伴随握手③直接发出。
  • 从客户端发出握手①,到服务器进程可以发送数据,至少需要多远?
    • 答:1.5 RTT。因为必须等待客户端的握手③到达服务器,服务器才能进入 ESTABLISHED 状态并发送数据。

三、 TCP 四次挥手核心机制

QQ20260615-095338

1. 详细交互过程与序号变化示例

假设主动关闭方(客户端)发起挥手时 seq = 9999,服务器收到后发送完 2200B 的单向数据:

  • 挥手 ①(Client Server,主动关闭)
    • SYN=0, ACK=1, FIN=1seq = 9999ack = 5600(消耗 1 个序号)
  • 挥手 ②(Server Client,被动关闭确认)
    • SYN=0, ACK=1, FIN=0seq = 5600ack = 10000(即 9999 + 1)。(此后服务器可以继续单向传送剩余数据,假设传了 2200B)
  • 挥手 ③(Server Client,被动方发送完毕准备关闭)
    • SYN=0, ACK=1, FIN=1seq = 7800(即 5600 + 2200),ack = 10000(消耗 1 个序号)
  • 挥手 ④(Client Server,最后确认)
    • SYN=0, ACK=1, FIN=0seq = 10000ack = 7801(即 7800 + 1)。

2. 🚨 核心考点:挥手数据与序号规则

  • 挥手 ① 和 挥手 ③即使不携带数据,也必须固定消耗 1 个序号(因为 FIN=1)。
  • 挥手 ②可以携带数据
  • 挥手 ④绝对不可以携带数据

四、 状态机转换与 TIME-WAIT 机制

1. 三次握手状态流转

QQ20260615-092120
  • 客户端CLOSED 发出① SYN-SENT 收到②并发出③ ESTABLISHED
  • 服务器CLOSED 打开监听 LISTEN 收到①并发出② SYN-RCVD 收到③ ESTABLISHED

2. 四次挥手状态流转(重点、难点)

QQ20260615-095534
  • 主动关闭方(Client)ESTABLISHED 发出① FIN-WAIT-1 收到② FIN-WAIT-2 收到③并发出④ TIME-WAIT 倒计时 2MSL CLOSED
  • 被动关闭方(Server)ESTABLISHED 收到①并发出② CLOSE-WAIT (关闭等待) 发送完数据并发出③ LAST-ACK 收到④ CLOSED

3. 💡 2MSL 与 TIME-WAIT 深度解析

  • MSL(Maximum Segment Lifetime,最大报文段寿命):由 TCP 协议规定的固定时间长度(如示例中 1 MSL = 800ms)。
  • 进入 TIME-WAIT 后的重置定时器规则:客户端收到挥手③并发出挥手④后,立即启动 TIME-WAIT 计时器,开始倒计时 2MSL如果在此期间,客户端重新收到了对方重传的挥手③(意味着客户端发出的挥手④丢失了,服务器在超时后重传了挥手③),客户端会重新发送挥手④并直接“重置”整个 2MSL 计时器

五、 释放连接阶段耗时分析(大题常考)

  • 从客户端发出挥手①,到客户端进程彻底进入 CLOSED 状态,至少需要多远?
    • 答:1 RTT + 2MSL
    • 解析:客户端发出① (0.5 RTT) 服务器收到并回复② (0.5 RTT) 客户端收到②。在这个时间点之后,还要等待服务器传输完数据并发出挥手③,客户端收到③后发出挥手④并进入 TIME-WAIT,最后死等 2MSL。在图示的最短理想情况下(服务器收到①后如果没有数据,紧接着就发③),从发出①到收到③正好经过 1 RTT,随后再经历 2MSL 的等待。
  • 从客户端发出挥手①,到服务器进程彻底进入 CLOSED 状态,至少需要多远?
    • 答:1.5 RTT
    • 解析:客户端发出① (0.5 RTT) 服务器收到并回复② (0.5 RTT) 客户端收到②并等待服务器发出③ (0.5 RTT) 客户端发出④并到达服务器 (0.5 RTT)。在最快情况下,服务器收到④后立刻转入 CLOSED

📝 特殊合并情况(考试简答题重点)

如果服务器进程在收到客户端的挥手①时,已经没有要发送给客户端的数据了,那么服务器可以将挥手②(ACK)和挥手③(FIN)合并为同一个报文段连续发出。此时,服务器的 CLOSE-WAIT 状态以及客户端的 FIN-WAIT-2 状态的持续时间都会极短,甚至接近于 0

QQ20260615-100223

TCP超时重传与可靠传输

QQ20260615-125845

一、 TCP 多连接与端口复用机制

1. 单端口支持多连接

  • 核心结论一个服务器端口(如 996)可以同时支持多个不同的 TCP 连接。

  • 连接的唯一标识:操作系统内核通过四元组来唯一确定一个 TCP 连接:

    TCP 连接 = {源 IP, 源端口, 目的 IP, 目的端口}

  • 底层实现:如图所示,服务器进程绑定端口 996,但当客户端 1(166.1.1.1:1111)和客户端 2(166.2.2.2:2222)分别发起连接时,操作系统内核会在传输层为它们分别创建独立的 Socket(套接字)对象(对象 1 和对象 2)。

2. 内存缓冲区分配

  • 每个 Socket 对象都拥有自己独立的发送缓冲区接收缓冲区
  • 这些缓冲区是在建立连接时由操作系统内核动态分配的,大小不固定,可以根据网络状况和系统预留进行动态扩容。

二、 TCP 传输底层原理:双端缓冲区与滑动窗口

TCP 报文段中的 seq(序号)、ack(确认号)以及 rwnd(接收窗口)是控制双端缓冲区读写的核心指针。

1. 握手阶段的参数初始化(以图 1、图 2 为例)

  • 握手 ①:客户端向服务器发送 seq=599(设置客户端进程初始序号),无有效 ack,携带有客户端的发送意愿。
  • 握手 ②:服务器为该连接分配接收缓冲区(假设大小为 8B),并在报文段中设置 seq=199(服务器初始序号),ack=600(期待收到客户端第 600 字节),并通告 rwnd=8
    • 关键联动:客户端收到握手 ② 后,根据服务器通告的 rwnd=8,将其发送窗口大小限制为 8
  • 握手 ③:客户端反馈 seq=600, ack=200,并通过 rwnd=10 告知服务器自己的接收缓冲区剩余大小为 10B。
QQ20260615-105137
QQ20260615-104104

2. 数据动态传输与窗口滑动(以图 4、图 7、图 8 为例)

  • 发送方的发送窗口:由接收方通告的 rwnd 决定。发送窗口内的字节允许直接发送,无需等待确认。
  • 数据打包发送
    • 客户端的发送缓冲区中存放着待发送数据。若当前发送窗口为 5(图 4 场景),它连续发送了两个报文段:
      • 报文段 1seq=603,携带 2B 数据(对应缓冲区中编号为 3、4 的字节)。
      • 报文段 2seq=605,携带 2B 数据(对应缓冲区中编号为 5、6 的字节)。
    • 此时服务器成功接收到 3, 4, 5, 6 字节并存入接收缓冲区,缓冲区中下一个期待接收的空位是 607
image-20260615125557419

三、 TCP 高效确认机制:累积确认与捎带确认

为了减少网络中纯控制报文占用的带宽,TCP 引入了以下优化确认机制:

1. 累积确认(Cumulative ACK)

  • 机制:接收方收到多个连续的有效报文段后,不需要逐个回复 ACK,而是只返回一个总的 ACK 报文段
  • 图示解析(图 5)
    • 客户端连续发出 seq=603 (2B)seq=605 (2B)
    • 服务器一并收到后,只返回了一个确认段:ack=607, rwnd=1。这代表 607 之前的所有数据(3, 4, 5, 6)都已正确接收
image-20260615125620943

2. 捎带确认(Piggybacking)

  • 机制:当接收方收到数据需要返回确认,且刚好自己也有数据要发送给对方时,将确认信息(ack)放入该数据报文段的首部中一同发送,从而将两个报文合并为一个。
  • 图示解析(图 6、7、8)
    • 服务器收到客户端发送的 seq=608 (2B) 数据。
    • 此时服务器应用层刚好有 5B 数据要发回客户端。
    • 服务器不再单独发送 ACK 段,而是直接发送数据报文段:seq=200, ack=610, rwnd=6, 数据5B。这里的 ack=610 就是捎带确认
    • 同理,客户端收到后,也在发出的 6B 数据中捎带了 ack=205 的确认信息。
image-20260615125634074
image-20260615125724568

四、 TCP 可靠传输机制:超时重传详解

TCP 每发送一个报文段,都会为其设置一个超时重传计时器(Retransmission Timer)。如果在规定时间内未收到确认,就会触发重传。考试中主要考察以下两种丢失场景:

场景 1:发送方发出的“数据报文段”丢失(图 9)

  1. 丢失:客户端发出 seq=618, 数据2B,但在网络中丢失。
  2. 触发:客户端的超时重传计时器到期,仍未收到来自服务器的 ACK。
  3. 动作重传并重置计时器。客户端重新发出完全相同的报文段 seq=618, 数据2B
  4. 完成:服务器这次顺利收到,并返回最新确认号 ack=620
image-20260615125656800

场景 2:接收方返回的“ACK报文段”丢失(图 10)

  1. 丢失:客户端发出 seq=620, 数据2B 顺利到达服务器。服务器正常回复 ack=622,但该 ACK 段在网络中丢失
  2. 触发:对客户端而言,它并不知道服务器已经收到。客户端的计时器到期后,判定报文可能丢失。
  3. 重传与去重
    • 客户端重新发送 seq=620, 数据2B
    • 服务器再次收到该段数据,对比内核指针后发现是重复数据,于是传输层直接丢弃该重复数据,以确保应用层拿到的字节流不重复
  4. 必须二次确认服务器必须再次返回 ACK 段(ack=622,通知客户端“我早就收到了,请不要再发了”。客户端收到后关闭计时器,重传成功结束。
image-20260615125759698

TCP拥塞控制

一、 滑动窗口与流量控制(Flow Control)

QQ20260615-125046

流量控制是端到端的通信控制,主要通过双端的滑动窗口和缓冲区协同实现:

  • 接收方(Receiver)
    • 维持一个接收窗口(rwndrcvwnd
    • 限制条件:接收窗口大小不能大于接收缓冲区的大小。
    • 物理意义:接收窗口像一个“框柱”的范围,框住的是接收方当前还允许接收的序号范围
  • 发送方(Sender)
    • 维持一个发送窗口
    • 双重限制条件(🚨 填空/选择题常考):
      1. 发送窗口不能大于发送缓冲区大小。
      2. 发送窗口不能大于接收方通告的接收窗口(rwnd
    • 物理意义:发送窗口“框住”的是发送缓冲区中已发送但尚未收到确认的数据,以及可以发送但尚未发送的数据

二、 TCP 可靠传输机制之重传演进

可靠传输由确认机制与重传机制共同支撑。

1. 确认机制的优化:推迟确认(Delayed ACK)

  • 累积确认规则:若收到 ack_seq = n,说明序号在 n 之前的所有字节均已正确接收。
  • 返回 ACK 的时机(推迟确认)
    • 为了减少网络开销,TCP 允许推迟发送确认,但推迟时间最多不能超过 0.5 秒(TCP 标准规定)。
    • 两个打破推迟、必须立刻返回 ACK 的场景
      1. 捎带确认:如果自己刚好也有数据要发送给对方,必须立即返回 ACK 段并“捎带”数据。
      2. 满两个 MSS:若连续收到两个长度为 MSS 的报文段,必须立即返回 ACK 段。
image-20260615153355505

2. 🚨 超时重传 vs 快重传(重难点、考试大题高频陷阱)

当传输过程中出现丢包(例如客户端连续发出 4 个报文段,第 2 个丢失)时,TCP 有两种重传应对策略:

🔹 策略 A:传统的超时重传

  • 现象:服务器收到第 1 个包,返回确认。因为第 2 个包丢了,服务器收到第 3、4 个包时属于失序报文段,触发服务器推迟确认延迟。
  • 缺点:客户端的发送窗口很快被填满并卡住。客户端必须死等超时重传计时器到期,才开始连续重发后面的报文段,导致传输效率低下。

🔹 策略 B:快重传(Fast Retransmit)与立即确认机制

  • 配套机制(立即确认):接收方每收到一个 TCP 报文段就立即返回一个 ACK。即使接收到的是一个失序(乱序)的报文段,也要立即返回当前期望的 ACK 段。这种失序触发的即时确认,直接导致了“冗余 ACK”的产生。
  • 快重传触发机制:当发送方连续收到 3 个完全相同的冗余 ACK(即总共收到 4 个相同的 ACK)时,判定该确认号对应的报文段已丢失,立刻重传该报文段,而不需要等待超时计时器到期。

🚨 关于“3个冗余ACK”的真题概念陷阱:

参考图片 QQ20260615-130654.jpg 中的手写批注:

当丢包发生后,服务器发回的第 1 个正常确认(比如 ack=623不属于冗余 ACK,它只是基准确认。

随后由于客户端继续发送后续报文,服务器又触发返回了 3 次一模一样的 ack=623。这后 3 个才叫“冗余(Duplicate)ACK”。加上第 1 个,发送方一共会连续收到 4 个相同的 ACK

image-20260615153415039

三、 流量控制 vs 拥塞控制(Congestion Control)

维度 流量控制(Flow Control) 拥塞控制(Congestion Control)
控制范围 局部(端到端) 全局(涉及整个网络中的所有主机、路由器)
核心目的 控制接收端的数据接收量,防止接收端缓冲区溢出。 减小整个网络中路由器的负载,防止网络发生崩溃。
控制窗口 接收窗口(rwnd:由接收方根据自身缓冲区大小通告。 拥塞窗口(cwnd:由发送方根据当前网络拥塞程度估算。

💡 发送窗口的终极决定公式

在实际的网络传输中,发送方的发送窗口上限值由双重因素共同决定,取两者的极小值:

发送窗口的上限值 = min [rwnd, cwnd]

📊 如何通过 ACK 状态判断网络拥塞程度?

  • 顺利收到每一个 ACK 网络网络不拥塞。
  • 收到冗余 ACK 并触发快重传 网络有点拥塞 适当缩小拥塞窗口。
  • 引发超时重传(Timer 到期) 网络严重拥塞 迅速收缩拥塞窗口。

四、 拥塞控制经典算法:慢开始与拥塞避免

QQ20260615-140621

发送方通过调整拥塞窗口 $cwnd$ 来动态适应网络。这里引入一个分界线阈值:慢开始门限(ssthresh

1. 慢开始算法(Slow Start)

  • 适用条件:当 $cwnd < ssthresh$ 时。
  • 增长规律:初始 $cwnd = 1$(单位为 MSS)。每收到一个 ACK,让 $cwnd = cwnd + 1$
  • 宏观表现:在每个传输轮次(RTT时间)里,如果没有丢包,拥塞窗口 $cwnd$ 会呈现指数规律增长1 → 2 → 4 → 8 → 16…)。

2. 拥塞避免算法(Congestion Avoidance)

  • 适用条件:当 $cwnd \ge ssthresh$ 时。
  • 增长规律在一个 RTT(传输轮次)内,即使收到多个 ACK,也只能让 $cwnd = cwnd + 1$
  • 宏观表现:窗口呈现线性规律增长(“加法增大”),使窗口平缓上升,探测网络极限。

五、 💥 发生严重拥塞(超时重传)后的收缩逻辑(大题必考计算)

QQ20260615-141058
QQ20260615-141415

根据图片 QQ20260615-140621.jpg141415.jpg 的标准图像轨迹,当网络发生严重拥塞并触发超时重传时,算法执行“乘法减小”收缩:

  1. 重置慢开始门限(ssthresh

    $$ssthresh = \frac{cwnd}{2}$$

    (注意:ssthresh 的值通常不能小于 2)

  2. 断崖式下跌

    将拥塞窗口 $cwnd$ 强行跌回 1

  3. 重新开始

    再次进入慢开始算法,从 $cwnd = 1$ 开始指数增长,直到到达新设定的 $ssthresh$ 分界线后,再次转为拥塞避免算法(线性增长)。

📉 图像实例解析(以图 5 为例):

  • 初始 ssthresh = 16$cwnd$ 从 1 开始慢开始指数增长。
  • 在第 4 个轮次时,$cwnd$ 刚好到达分界线 16,转为拥塞避免(线性增加:16 → 17 → 18…)。
  • $cwnd$ 线性增长到 24 时,网络发生超时重传(说明严重拥塞)。
  • 调整策略:新 ssthresh = 24/2 = 12;同时 $cwnd$ 跌落回 1
  • 接下来的轮次$cwnd$ 从 1 指数增长到 12(慢开始),到达 12 后再次改为线性步进增长(拥塞避免)。

TCP 快重传与快恢复算法

一、 快重传与快恢复的触发背景

  • 拥塞程度判定
    • 引发超时重传 网络发生严重拥塞
    • 收到 3 个冗余 ACK(触发快重传) 报文段虽然丢失,但后续报文仍能顺利到达接收方并触发回传确认,说明网络只是“有点拥塞”
  • 优化思想:既然网络没有发生全面瘫痪,发送窗口就不需要像发生超时那样“断崖式跌回 1”。可以通过快恢复算法使网络吞吐量维持在一个较高水平。

二 快恢复算法(Fast Recovery)核心窗口调整逻辑

QQ20260615-154002

当发送方连续收到 3 个完全相同的冗余 ACK 时,触发快重传,紧接着进入快恢复阶段(以当前主流的 TCP Reno 版本为准):

  1. 慢开始门限(ssthresh)减半

    $$ssthresh_{new} = \frac{cwnd_{current}}{2}$$

    (注:在真题中,若除不尽通常向下取整,且 ssthresh 的值不能小于 2)

  2. 拥塞窗口(cwnd)同步减半

    直接将新拥塞窗口设为改变后的门限值:

    cwndnew = ssthreshnew

  3. 直接进入拥塞避免

    跳过“从 1 开始的慢开始阶段”,直接从减半后的 cwnd 开始执行拥塞避免算法(即按传输轮次 RTT 进行“加法增大”线性增长)。

三、 经典对比:TCP Reno 版本 vs TCP Tahoe 版本

在考试大题中,必须看清题目要求的是老旧的 Tahoe 版本还是现代的 Reno 版本,它们在收到 3 个冗余 ACK 后的表现截然不同:

  • TCP Tahoe 版本(已废弃不用)
    • 一刀切策略:无论是收到 3 个冗余 ACK 还是超时重传,全部一律ssthresh 减半,并将 cwnd 强行降回 1,重新执行慢开始
  • TCP Reno 版本(当前标准)
    • 分情况区别对待
      • 遇到超时重传ssthresh 减半,cwnd 降回 1,走慢开始
      • 遇到3个冗余 ACK:执行快恢复ssthreshcwnd 同时减半,直接走拥塞避免(宏观图像上表现为一个“V字形半折返”,而不是跌落到底部)。

四、 💥 实例拆解分析(基于 RTT 传输轨迹大题思维)

QQ20260615-154035

结合图片 QQ20260615-154035.jpg 的真实传输轨迹,我们来拆解动态计算过程:

1. 拥塞发生点(第 12 轮次)

  • 状态:此时经过加法增大后,拥塞窗口增大到了 cwnd = 24
  • 事件:由于网络出现轻度丢包,发送方在这个轮次内触发并集齐了 3 个冗余 ACK
  • 触发动作(快重传 + 快恢复)
    • 立即重传丢失的那个报文段。
    • 更新门限:ssthresh = 24/2 = 12
    • 更新窗口:cwnd = ssthresh = 12

2. 快恢复平稳过渡(第 13 轮次)

  • 状态:进入第 13 个 RTT,此时 ssthresh = 12cwnd = 12
  • 传输行为:由于 cwnd = 12,发送方在这个 RTT 内连续发送 12 个报文段,并顺利连回了 12 个确认 ACK。
  • 算法适用:因为 cwnd ≥ ssthresh(此处刚好相等),从这一轮次开始,正式切换并执行拥塞避免算法

3. 进入线性增长(第 14、15 轮次)

  • 第 14 RTT:根据拥塞避免的“加法增大”规律,一个完整的传输轮次(RTT)结束后,窗口只能 +1

    cwnd = 12 + 1 = 13

  • 第 15 RTT:同样保持线性步进。

    cwnd = 13 + 1 = 14

📝 总结大题得分技巧

做计算题时,先通过“超时”还是“3个冗余ACK”判定是断崖下跌(cwnd = 1)*还是*高位减半(cwnd = ssthresh = cwnd/2。如果是后者,在改变后的第一个 RTT 里窗口大小不变(用于发送并收回减半后的满载数据包),从下一个 RTT 开始,保持每轮次 +1 稳步线性上扬即可。

应用层

DNS(域名系统)

一、 DNS 的核心概念与基本特征

1. 域名与 IP 地址的关系

  • 域名(Domain Name):给互联网上的主机/路由器取的一个层次结构的名字(民间俗称的“网址”),便于人类记忆(如 www.cskaoyan.com)。
  • IP 地址:便于计算机处理的数字化标识(如 111.124.194.228)。
  • DNS 的作用:作为分布式数据库,将便于人类记忆的域名转换成便于计算机处理的 IP 地址

2. DNS 系统设计:集中式 vs 分布式

  • 集中式系统(Centralized System):所有数据和服务集中在少数服务器上。
    • 若 DNS 采用集中式:会导致全球计算机访问过载、单点故障导致整个网络瘫痪。
  • 分布式系统(Distributed System):数据和服务分散在多台服务器上,协同工作。DNS 采用此架构以实现高可用和去中心化。

3. DNS 的工作特性

  • 工作模式:采用 C/S(客户端/服务器) 工作方式。
    • 注意:DNS 客户端和服务器的角色是相对的。一台 DNS 服务器在向上级查询时,会充当客户端。
  • 协议与端口:属于应用层协议,在传输层通常基于 UDP 协议进行封装,使用固定端口 53

二、 域名的层次结构与顶级域名(TLD)分类

为了便于级别授权与管理,域名被设计为树状的层次结构

1. 层次划分(从右往左看)

  • 根域名(.):在顶级域名之上,被记作一个“点”,通常省略不写。由 ICANN 统一管理和划分。
  • 顶级域名(TLD):如 cn(国家)、com(公司)。
  • 二级域名:如 edu.cn(教育网)、cskaoyan.com
  • 三级域名:如 pku.edu.cnwww.cskaoyan.com
image-20260615175526609

2. 顶级域名(TLD)三大分类

  1. 国家顶级域名(ccTLD):由各个国家/地区独立管理。如 .cn(中国,由 CNNIC 管理)、.us(美国)、.jp(日本)。
  2. 通用顶级域名(gTLD):表征组织性质。
    • .com:商业/公司类组织(Commercial)。
    • .net:网络服务机构(如 battle.net)。
    • .org:非营利性组织(如 wto.org)。
    • .edu / .gov:美国教育机构/政府专用(历史保留)。
  3. 基础结构域名 / 反向域名全球只有 1 个,即 .arpa
    • 作用:用于反向域名解析(即从 IP 地址 域名)。
    • 示例:查询 8.8.8.8 对应的域名,工具会访问反向域名 8.8.8.8.in-addr.arpa

三、 四类域名服务器系统

QQ20260615-171944

通过“根 顶级 权限”逐层查询,一定可以查到任何有效域名的 IP 地址。

  1. 根域名服务器:记录并维护所有顶级域名服务器的 IP 地址。
    • 🚨 易错概念陷阱:全世界有 13 组根域名服务器(每组绑定一个固定 IP,如 1.1.1.1)。但 “每组”绝非单台主机,而是部署在全球多地的大量镜像节点。它们通过 Anycast(任播) 技术支持就近访问、冗余和容灾。
  2. 顶级域名服务器(TLD 服务器):管理在该顶级域名下注册的所有二级域名(如负责 .com 的服务器)。
  3. 权限域名服务器(授权域名服务器):负责将其管辖区内的主机名转换为 IP 地址(如 qq.com 权限服务器 记录了下属 mail.qq.com 的 IP)。
  4. 本地域名服务器(默认 DNS 入口)
    • 用户侧的默认 DNS 入口。主机入网时通过 DHCP 协议自动获取其 IP。
    • 通常由当地 ISP(电信/联通)、高校或企业自建。

四、 提高查询效率的关键:DNS 缓存机制

  • 本地域名服务器命中:若本地域名服务器中存有对应的 DNS 缓存,则直接返回结果,无需向外发起层层查询。
  • 生存时间(TTL, Time to Live):DNS 缓存记录会设置过期时间,到期后自动失效,以保证域名变更时数据能够更新。
  • 多级缓存:除了本地服务器,用户主机的操作系统级 DNS 解析器(Resolver) 以及 浏览器(如 Chrome) 内部都会维护应用级的 DNS 缓存。

五、 🚨 域名解析核心过程:递归查询 vs 迭代查询(必考大题)

在缓存未命中的情况下,本地域名服务器向外查询主要有两种方式:

1. 递归查询(Recursive Query)

  • 通俗口诀:“你问我问题,我想方设法帮你找到最终答案。”(一问一答,一查到底
  • 流程轨迹
    • 主机 本地域名服务器
    • 本地 根域名服务器 顶级 权限(由被询问方代替查询方继续向前问)
    • 最终结果沿原路返回。
  • 缺点:靠前的服务器(如根服务器)负担极重,极少在公网服务器之间采用。
QQ20260615-172951

2. 迭代查询(Iterative Query)

  • 通俗口诀:“你问我问题,我如果不知道最终答案,就告诉你‘下一个该去问谁’。”(指引方向,自主接力
  • 标准流程轨迹(以查询 mail.qq.com 为例):
    1. 主机本地:“mail.qq.com 的 IP 是多少?”
    2. 本地回复:“我不知道,你去问 .com 顶级服务器(IP: 3.3.3.3)。”
    3. 本地顶级顶级回复:“我不知道,你去问 qq.com 权限服务器(IP: 4.4.4.4)。”
    4. 本地权限权限给出最终权威解析:“它的 IP 是 7.7.7.7。”
    5. 本地将结果登记到缓存,并回复给主机
QQ20260615-173102
0%