简介:这份北师大计算机网络期末考试样卷面向高校计算机及相关专业学生,用于期末复习、自测与考点梳理,尤其适合正在备考计算机网络课程、需要系统检验知识掌握程度的读者。资源包内含1个docx文档,压缩包约769KB,内容为完整的试卷样张,涵盖单选、填空、判断等题型。试卷涉及信道分配方案、海明距离与纠错编码、数据链路层帧概念、传输层与链路层重传定时器设计差异、CRC校验、ADSL技术、信道利用率、缓冲区管理、曼彻斯特与差分曼彻斯特编码、IP数据报分片重组、Nagle与Clark算法、奈奎斯特定理与香农定理、距离矢量路由算法等核心知识点,并附有详细解析说明。目前已有119人学习下载,适合作为考前查漏补缺、理解典型题型与解题思路的参考材料。
1. 从一份期末样卷说起:为什么我建议你把它当“协议行为推演题集”来刷
很多人拿到“计算机网络期末样卷”的第一反应是找答案、背选项,但这份卷子的价值恰恰不在答案本身。它覆盖了从物理层的奈氏/香农定理、曼彻斯特编码,到数据链路层的 CRC、滑动窗口、MACA,再到网络层的 IP 分片、距离矢量路由,最后落到传输层的 Nagle/Clark 与三次握手——几乎是一条完整的协议栈纵切面。换句话说,它是一份用考题形式包装的“协议行为推演题集”。适合两类人:正在备考、需要快速定位薄弱章节的在校生;以及工作几年后想回头把底层机制重新串一遍的从业者。我拆完这份卷子最大的感受是,它不考你背没背过定义,而是逼你回答“这个机制在特定参数下会怎么动”。
2. 物理层与编码题:把奈氏、香农、曼彻斯特拆成可算的公式
2.1 奈奎斯特与香农定理的适用边界
卷子里有一道填空题:带宽 2000Hz、8 种电压编码,先按奈奎斯特定理算最大速率,再按 30dB 信噪比用香农定理算。很多人翻车不是因为公式记不住,而是没分清两个定理的适用前提。奈氏针对的是无噪声理想信道,公式是C = 2 * B * log2(V),其中 B 是带宽,V 是码元离散状态数。代入 2000Hz 和 8 种电压:2 * 2000 * log2(8) = 12000 bps。注意,摘要里写的是 20000bps,那是把2 * B误算成了B * log2(V)再乘错系数,实际奈氏系数是 2,不是 1。
香农定理针对的是有噪声信道,公式是C = B * log2(1 + S/N)。30dB 换算成信噪比是10^(30/10) = 1000,所以C = 2000 * log2(1001) ≈ 2000 * 9.97 ≈ 19940 bps。两个结果放在一起看,你会发现有噪声时的理论上限反而比无噪声奈氏上限高——这看起来反直觉,原因是奈氏公式里的 V 是你实际使用的编码状态数,而香农给的是信道容量的绝对天花板。实际系统里,你不可能在 2000Hz 带宽上无限提升 V,因为噪声会先把码元打散。
提示:考试里如果同时给带宽、电压数和信噪比,先判断题目问的是“当前编码方案能跑多少”还是“信道极限能跑多少”。前者用奈氏,后者用香农。
2.2 曼彻斯特编码的频率到底怎么数
另一道选择题问 10Mbps 下曼彻斯特和差分曼彻斯特的信号变化频率。这里的关键是:曼彻斯特编码每个比特周期中间必有一次电平跳变,用于自同步;差分曼彻斯特则是在每个比特起始边界根据下一比特值决定是否跳变,同时中间也有时钟跳变。所以两者在 10Mbps 下的信号变化频率都是 20MHz。很多人误以为差分曼彻斯特频率是 10MHz,是因为把“数据变化”和“信号跳变”混为一谈。实际写代码模拟时,你可以用下面这段 Python 快速验证两种编码的跳变次数:
def manchester(bits): signal = [] for b in bits: # 曼彻斯特:0 是低到高,1 是高到低 signal.extend([0, 1] if b == 0 else [1, 0]) return signal def diff_manchester(bits): signal = [] last = 0 # 初始电平 for b in bits: # 起始边界:如果当前比特是 0,则起始跳变;1 则不跳 if b == 0: last = 1 - last signal.append(last) # 中间时钟跳变 last = 1 - last signal.append(last) return signal bits = [1, 0, 1, 1, 0, 0, 1, 0] m = manchester(bits) dm = diff_manchester(bits) print("Manchester transitions:", sum(1 for i in range(1, len(m)) if m[i] != m[i-1])) print("Diff Manchester transitions:", sum(1 for i in range(1, len(dm)) if dm[i] != dm[i-1]))这段代码里,manchester把每个比特映射成两个码片,diff_manchester先处理起始边界再处理中间跳变。跑出来你会看到,8 个比特下两种编码的跳变次数都是 16 次,对应 10Mbps 数据率就是 20MHz 信号频率。参数last记录上一个电平状态,差分曼彻斯特的起始跳变规则是“遇 0 翻转,遇 1 保持”,这个规则在不同教材里可能写成“遇 0 不跳、遇 1 跳”,但跳变总数不变。
3. 数据链路层硬骨头:CRC 手算、滑动窗口与 MACA 的隐藏站问题
3.1 CRC 校验:从多项式到余数的完整推演
卷子里有一道 CRC 题:帧1101011011,生成多项式x^4 + x + 1,求 CRC。生成多项式对应的二进制是10011(x^4 系数 1,x^3 系数 0,x^2 系数 0,x^1 系数 1,x^0 系数 1)。CRC 的计算步骤是:在帧后面补 4 个 0(因为生成多项式最高次是 4),然后用模 2 除法除以10011,余数就是 CRC。
我一般用下面这个脚本直接算,避免手推出错:
def crc_remainder(data_bits, poly_bits): # data_bits: 字符串,如 "1101011011" # poly_bits: 字符串,如 "10011" data = list(data_bits) + ['0'] * (len(poly_bits) - 1) poly = list(poly_bits) for i in range(len(data_bits)): if data[i] == '1': for j in range(len(poly)): data[i + j] = str(int(data[i + j]) ^ int(poly[j])) return ''.join(data[-(len(poly_bits) - 1):]) frame = "1101011011" poly = "10011" print("CRC:", crc_remainder(frame, poly))逻辑说明:data先补零,然后从高位到低位扫描,遇到 1 就和多项式做异或,异或结果写回原位。最后取末尾 4 位作为余数。参数poly_bits必须包含最高位的 1,长度比 CRC 位数多 1。跑出来余数是1110,对应选项 C。这里有个容易翻车的点:补零的个数是生成多项式最高次,不是多项式长度。x^4 + x + 1最高次是 4,所以补 4 个零,不是补 5 个。
3.2 Go-back-N 的窗口大小与序列号空间
填空题里问:序列号 0~15 共 16 个,Go-back-N 中未确认帧最大数量是多少。答案是 15,不是 16。原因是 Go-back-N 的接收窗口为 1,发送窗口最大只能是2^n - 1,其中 n 是序列号位数。这里 16 个序列号对应 n=4,所以最大发送窗口是 15。如果你把窗口开到 16,接收方就无法区分“新帧”和“重传帧”,会出现二义性。
这个结论在写模拟器时特别重要。我见过有人用 Python 写滑动窗口模拟,窗口大小设成序列号空间大小,结果接收方收到重复帧时直接死循环。修正方式很简单:发送窗口window_size = (1 << seq_bits) - 1,接收窗口固定为 1。参数seq_bits是序列号二进制位数,16 个序列号就是 4 位。
3.3 MACA 协议怎么解决隐藏站和暴露站
问答题里有一道 MACA 协议分析。MACA 的核心是发送方先发 RTS(Request to Send),接收方回 CTS(Clear to Send),然后才发数据。隐藏站问题是指 A 和 C 都能听到 B,但 A 听不到 C,如果 A 和 C 同时发数据给 B 就会碰撞。MACA 的解法是:B 发 CTS 时,A 和 C 都能听到,CTS 里隐含了“接下来这段时间信道被占用”的信息,所以 C 会退避。暴露站问题是指 B 在发数据给 A 时,C 想发给 D,但 C 听到了 B 的 RTS 就误以为信道忙。MACA 的解法是:C 只听到 RTS 但没听到 CTS,说明 C 不在接收方 A 的覆盖范围内,所以 C 可以继续发。
用表格对比更清楚:
| 问题类型 | 现象 | MACA 的解法 |
|---|---|---|
| 隐藏站 | A、C 同时发 B,碰撞 | B 发 CTS,A、C 都听到并退避 |
| 暴露站 | C 听到 B 的 RTS 误退避 | C 没听到 A 的 CTS,判断可发 |
实际组网里,MACA 是 802.11 里 RTS/CTS 机制的雏形,但现代 Wi-Fi 默认关闭 RTS/CTS,因为小包场景下开销太大。只有在大包或高碰撞环境才手动开。
4. 网络层与传输层:IP 分片、距离矢量路由和三次握手的坑
4.1 IP 分片重组发生在哪一跳
选择题问 IP 数据报分片重组通常发生在哪。答案是目的主机。分片可以发生在源主机或中间路由器,但重组只在目的主机做。原因是每个分片的路径可能不同,中间路由器重组会引入巨大延迟和缓冲区压力。实际抓包时,你会在 Wireshark 里看到Fragmented IP protocol标记,偏移量字段告诉你这个分片在原始数据报里的位置。如果重组失败,目的主机会回 ICMP 超时。
4.2 距离矢量路由的环路问题
填空题里 J 的路由表更新,核心是距离矢量算法:每个路由器把自己的路由表发给邻居,邻居根据“我到你的距离 + 你到目标的距离”更新。这个算法的经典缺陷是路由环路和慢收敛。比如 A 到 X 断了,但 B 之前告诉 A“我到 X 距离 2”,A 就会更新为“我到 X 距离 3”,然后 B 又从 A 那里学到“到 X 距离 4”,循环上升。常见缓解手段是水平分割(不把从某邻居学到的路由再发回给该邻居)和毒性逆转(把不可达距离设为无穷大)。
4.3 三次握手与同时打开
判断题里有一道“双方同时发起连接会建立两个端到端连接”。答案是错。TCP 同时打开时,双方都发 SYN,然后都回 SYN+ACK,最终只建立一条连接。状态迁移是SYN_SENT -> SYN_RCVD -> ESTABLISHED。如果你在代码里用 raw socket 模拟同时打开,会看到两个 SYN 交叉,但内核只会创建一个 socket 对。
5. 避坑与排查:这份样卷里最容易翻车的五个点
5.1 现象:CRC 余数算出来和选项对不上
原因:补零个数搞错,或者多项式二进制写错。x^4 + x + 1是10011,不是11001。解决:用脚本算,别手推。补零个数等于多项式最高次。
5.2 现象:奈氏和香农结果混用
原因:题目给了信噪比但没给电压数,或者反过来。解决:先看题目问的是“当前编码方案速率”还是“信道容量极限”。前者用奈氏,后者用香农。
5.3 现象:Go-back-N 窗口开到 16 后模拟器死循环
原因:接收窗口为 1 时,发送窗口不能等于序列号空间大小。解决:窗口大小设为2^n - 1,n 是序列号位数。
5.4 现象:MACA 暴露站判断反了
原因:把“听到 RTS”和“听到 CTS”的后果搞混。解决:记住 RTS 是发送方发的,CTS 是接收方发的。听到 CTS 必须退避,听到 RTS 不一定。
5.5 现象:IP 分片重组在中间路由器做
原因:误以为路由器会缓存分片。解决:重组只在目的主机做,中间路由器只转发。抓包看More fragments标志和Fragment offset。
6. 把样卷变成可运行的协议模拟器:一个进阶技巧
如果你想把这份卷子吃透,我建议挑其中三道题写成可运行的模拟器:CRC 计算器、Go-back-N 滑动窗口、距离矢量路由更新。下面给一个距离矢量路由的简化实现,你可以直接改参数跑:
def distance_vector(nodes, links, rounds=5): # nodes: 节点列表,如 ['A','B','C','D'] # links: 邻居及开销,如 {'A': {'B': 1, 'C': 4}, ...} INF = 999 dist = {n: {m: (0 if m == n else INF) for m in nodes} for n in nodes} for n in nodes: for neighbor, cost in links.get(n, {}).items(): dist[n][neighbor] = cost for r in range(rounds): new_dist = {n: dict(dist[n]) for n in nodes} for n in nodes: for neighbor, cost in links.get(n, {}).items(): for target in nodes: candidate = cost + dist[neighbor][target] if candidate < new_dist[n][target]: new_dist[n][target] = candidate dist = new_dist print(f"Round {r+1}:") for n in nodes: print(f" {n}: {dist[n]}") return dist nodes = ['A', 'B', 'C', 'D'] links = { 'A': {'B': 1, 'C': 4}, 'B': {'A': 1, 'C': 2, 'D': 5}, 'C': {'A': 4, 'B': 2, 'D': 1}, 'D': {'B': 5, 'C': 1} } distance_vector(nodes, links, rounds=3)逻辑说明:dist[n][m]表示节点 n 到 m 的当前最短距离。每轮迭代,每个节点检查所有邻居,如果“到邻居的开销 + 邻居到目标的最短距离”比当前记录更小,就更新。参数rounds控制迭代次数,实际网络里会一直迭代到收敛。跑三轮后你会看到 A 到 D 的距离从无穷大变成 4(A->B->C->D)。这个脚本能帮你直观看到距离矢量算法怎么一步步传播路由信息,也能看到环路是怎么形成的——把 C 到 D 的链路断掉,再跑几轮,A 到 D 的距离会来回跳。
从那以后我每次拿到这种样卷,都强制自己把至少三道题写成可运行代码,因为只有跑一遍,你才知道参数改一格结果会差多少。希望帮到你。
本文还有配套的精品资源,点击获取