Python网络拓扑实验:从邻接矩阵到NetworkX的完整实现
发布时间:2026/10/8 4:56:55 作者:尧图编辑部 阅读量:1,286

简介这份资源面向计算机网络课程学习者与实验实践者围绕基于Python的网络拓扑实验展开重点解决传输机制实验中拓扑运行与文件收发功能的实现问题。包内共96个文件以C语言源码28个与头文件40个为核心辅以14个Shell脚本、4个Python脚本及Makefile、数据文件等压缩包约2.65MB覆盖实验代码、报告文档与演示素材便于直接编译运行与对照修改。实验内容包含运行给定网络拓扑tcp_topo.py以及修改tcp_apps.c与tcp_stack.py实现文件收发配套实验报告与讲义可帮助理解协议栈设计与调试思路。目前已有172人学习适合需要完成课程设计、掌握网络传输机制实现细节的读者参考也可作为实验排错与代码结构梳理的辅助材料。1. 网络拓扑实验为什么值得用 Python 重做一遍很多做网络运维或后端开发的朋友第一次接触网络拓扑这个词往往是在机房对着交换机堆叠图发呆或者被问到你能不能把当前网络结构画出来。传统做法是打开 Visio 手动拖图标改一次 IP 就要重画一遍节点超过二十个基本就放弃维护了。而用 Python 做网络拓扑实验核心价值在于把拓扑从一张静态图片变成一份可计算的数据结构——节点是对象链路是边带宽、延迟、丢包率都是可以参与运算的属性。这份实验编号 100010470本质上就是让你用代码把网络抽象成图再在图上跑算法、做可视化、验证连通性。适合谁刚学完 Python 基础语法、想找个能落地的练手项目的人需要自动化生成拓扑图的运维以及准备网络仿真或 SDN 课程作业的学生。下面我按自己实际跑过的路径把选型、建模、算法、可视化到避坑完整讲一遍。2. 用 Python 建模网络拓扑从邻接矩阵到 NetworkX 图对象2.1 为什么先讲邻接矩阵而不是直接上画图库网络拓扑在数学上就是一个图 G(V,E)V 是节点路由器、交换机、主机E 是边物理链路或逻辑连接。Python 里表示图最原始的方式是邻接矩阵一个 N×N 的二维数组matrix[i][j]1表示 i 和 j 之间有链路0表示没有。热搜里python构建邻接矩阵之所以被频繁搜索是因为它是所有图算法的基础输入格式。但纯手写矩阵有个致命问题节点一多矩阵稀疏得可怕100 个节点的矩阵有 10000 个格子实际链路可能只有 150 条剩下 9850 个零全是浪费。所以实验里我一般先用矩阵做小规模验证节点超过 15 个就切到邻接表或直接上 NetworkX。先看最小可运行的矩阵构建代码这段可以直接抄# 用嵌套列表构建 5 节点网络的邻接矩阵 # 节点编号 0-41 表示直连0 表示不通 nodes 5 adj_matrix [[0] * nodes for _ in range(nodes)] # 定义链路(起点, 终点, 是否双向) links [(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)] for u, v in links: adj_matrix[u][v] 1 adj_matrix[v][u] 1 # 无向图对称赋值 # 打印矩阵方便肉眼核对 for row in adj_matrix: print(row)逻辑说明[[0]*nodes for _ in range(nodes)]这种写法必须用列表推导不能写成[[0]*nodes]*nodes后者五个子列表是同一个对象的引用改一个全变。参数说明links列表里每个元组代表一条链路如果是有向图比如单向光纤去掉adj_matrix[v][u]1那行即可。跑完输出应该是五行五列对角线全零对称位置成对出现 1。2.2 NetworkX 的安装与图对象初始化矩阵只适合验证算法逻辑真正做拓扑实验得用 NetworkX。安装命令一行pip install networkx matplotlib这里注意热搜里python安装numpy库的方法和python安装是高频词但 NetworkX 不强制依赖 NumPy它自己用字典存邻接关系。如果你环境里 NumPy 版本太老导致 matplotlib 报错先pip install -U numpy升级。初始化一个拓扑图import networkx as nx # 创建无向图有向图用 nx.DiGraph() G nx.Graph() # 批量添加节点附带属性 G.add_nodes_from([ (0, {type: router, ip: 10.0.0.1}), (1, {type: switch, ip: 10.0.0.2}), (2, {type: host, ip: 10.0.0.3}), (3, {type: host, ip: 10.0.0.4}), ]) # 添加带权重的边weight 可表示延迟(ms)或带宽(Mbps) G.add_edge(0, 1, weight2, bandwidth1000) G.add_edge(1, 2, weight5, bandwidth100) G.add_edge(1, 3, weight3, bandwidth100) print(G.nodes[0]) # 查看节点属性 print(G.edges[0, 1]) # 查看边属性 print(nx.is_connected(G)) # 判断全图连通性逻辑说明add_nodes_from接受元组列表第二个元素是属性字典这样节点不只是编号还带类型和 IP后面画图可以按类型上色。参数说明weight我习惯用来存延迟bandwidth存带宽这两个字段在最短路径和瓶颈分析里都会用到。nx.is_connected返回布尔值如果为 False 说明网络分裂成了孤岛这是拓扑实验里第一个要检查的健康指标。2.3 从 CSV 或字典批量导入真实拓扑实验里不会只让你手敲四个节点常见做法是从文件读。假设你有一份topo.csv每行是源节点,目标节点,延迟import csv import networkx as nx G nx.Graph() with open(topo.csv, encodingutf-8) as f: reader csv.reader(f) next(reader) # 跳过表头 for row in reader: u, v, delay int(row[0]), int(row[1]), float(row[2]) G.add_edge(u, v, weightdelay) print(f节点数: {G.number_of_nodes()}, 链路数: {G.number_of_edges()})逻辑说明next(reader)跳过表头是血泪经验不跳的话第一行字符串转 int 直接抛 ValueError。参数说明weight用 float 存延迟因为真实链路延迟常有小数。如果 CSV 里节点是字符串名称如 R1、SW2把int(row[0])改成row[0]即可NetworkX 节点可以是任意可哈希对象。3. 在拓扑上跑算法连通性、最短路径与单点故障排查3.1 连通性判断与割点识别拓扑建好之后第一个要回答的问题是这张网是不是全通的如果不通断在哪里NetworkX 提供了现成函数import networkx as nx # 假设 G 已经构建好 if not nx.is_connected(G): # 找出所有连通分量 components list(nx.connected_components(G)) print(f网络分裂成 {len(components)} 个孤岛:) for i, comp in enumerate(components): print(f 孤岛{i}: {sorted(comp)}) else: print(全网连通) # 找割点删掉它网络就分裂的节点 cut_nodes list(nx.articulation_points(G)) print(f关键节点(割点): {cut_nodes})逻辑说明connected_components返回集合的列表每个集合是一个孤岛。articulation_points找出所有割点这些节点一旦宕机网络就断成两半是运维重点保护对象。参数说明无向图才能直接调is_connected有向图要用nx.is_strongly_connected。如果图里有权重为 0 的边不影响连通性判断但会影响最短路径。3.2 最短路径与延迟累加拓扑实验里最常被问的就是从 A 到 B 走哪条路延迟最低。Dijkstra 是标配# 计算节点 0 到节点 4 的最短路径按 weight 累加 try: path nx.dijkstra_path(G, source0, target4, weightweight) cost nx.dijkstra_path_length(G, source0, target4, weightweight) print(f最短路径: {path}) print(f总延迟: {cost} ms) except nx.NetworkXNoPath: print(两点之间不可达)逻辑说明dijkstra_path返回节点列表dijkstra_path_length返回权重和。必须用 try-except 包住因为不连通时抛NetworkXNoPath不处理直接崩。参数说明weightweight指定用边的哪个属性做代价如果你的边属性叫delay就改成weightdelay。如果所有边权重相同用nx.shortest_path更快它默认按跳数算。3.3 模拟单点故障删节点后看影响范围这是拓扑实验里最有价值的一步——提前知道哪个节点挂了会波及多少设备import networkx as nx def simulate_failure(G, failed_node): 模拟某节点宕机返回受影响的节点数和新的连通分量 H G.copy() # 必须深拷贝不能改原图 H.remove_node(failed_node) if nx.is_connected(H): return 0, [list(H.nodes())] comps list(nx.connected_components(H)) # 受影响节点 总数 - 最大连通分量大小 affected H.number_of_nodes() - max(len(c) for c in comps) return affected, [sorted(c) for c in comps] for node in G.nodes(): affected, comps simulate_failure(G, node) print(f节点 {node} 宕机 - 影响 {affected} 个节点, 分裂为 {len(comps)} 块)逻辑说明G.copy()是深拷贝直接remove_node会永久改掉原图这是新手最常翻车的地方。参数说明affected的计算方式是总节点数减去最大连通分量因为最大那块还算活着其余都是被隔离的。跑完你会得到一张表哪个节点是单点故障一目了然。4. 拓扑可视化用 Matplotlib 画出能进报告的网络图4.1 三种布局算法的选择与参数NetworkX 画图靠布局算法决定节点坐标常用三种布局函数适用场景关键参数节点多了会怎样spring_layout通用力导向k越大节点越散超过 50 个节点变毛线球circular_layout环形/骨干网无节点均匀分布边交叉多kamada_kawai_layout中小规模美观无计算慢30 节点内效果好我一般先用spring_layout看整体如果边太乱就换kamada_kawai。代码import matplotlib.pyplot as plt import networkx as nx # 按节点类型上色 color_map {router: #e74c3c, switch: #3498db, host: #2ecc71} node_colors [color_map.get(G.nodes[n].get(type, host), #95a5a6) for n in G.nodes()] pos nx.spring_layout(G, seed42, k0.8) # seed 固定保证每次图一样 plt.figure(figsize(10, 7)) nx.draw_networkx_nodes(G, pos, node_colornode_colors, node_size600) nx.draw_networkx_edges(G, pos, width1.5, alpha0.7) nx.draw_networkx_labels(G, pos, font_size10) # 把边权重标在中间 edge_labels nx.get_edge_attributes(G, weight) nx.draw_networkx_edge_labels(G, pos, edge_labelsedge_labels, font_size8) plt.axis(off) plt.tight_layout() plt.savefig(topology.png, dpi150) plt.show()逻辑说明seed42是后悔药不固定种子每次布局随机报告里的图和你昨天看的不一样没法对比。参数说明k0.8控制节点间距值越大越散节点重叠时调大。node_size600是经验值节点少于 20 个用 600-800多了降到 200-300。dpi150保证截图清晰投稿或报告够用。4.2 横坐标标签太密集怎么处理热搜里python画图横坐标太密集是高频痛点拓扑图虽然节点标签不在横轴上但如果你额外画了延迟分布柱状图横轴节点名就会挤成一团。解决办法两个旋转标签或抽样显示。import matplotlib.pyplot as plt labels [fN{i} for i in range(30)] values list(range(30)) plt.figure(figsize(12, 5)) plt.bar(labels, values) plt.xticks(rotation45, haright, fontsize8) # 旋转45度右对齐 # 或者每隔3个显示一个 plt.xticks(range(0, 30, 3), [labels[i] for i in range(0, 30, 3)]) plt.tight_layout() plt.savefig(bar.png, dpi150)逻辑说明rotation45是最通用的解法haright让标签末端对齐刻度不然会偏。参数说明fontsize8在 30 个标签时刚好不重叠超过 50 个建议用抽样法range(0,30,3)里的 3 就是抽样步长。5. 避坑与排查拓扑实验里最容易翻车的五个地方5.1 现象is_connected报错NetworkXNotImplemented原因对DiGraph调了无向图的连通性函数。有向图必须用nx.is_strongly_connected或nx.is_weakly_connected。解决先确认图类型print(type(G))有向图改函数名或者用G.to_undirected()转成无向图再判断。5.2 现象最短路径算出来是空列表或直接抛异常原因源节点或目标节点根本不在图里或者两点之间确实没有路径。解决先if source in G and target in G检查节点存在再用 try-except 捕获NetworkXNoPath。另外注意如果边没有weight属性而你指定了weightweightNetworkX 会默认权重为 1不报错但结果不对这是最隐蔽的坑。5.3 现象画出来的图节点全挤在中心边看不见原因spring_layout的k参数太小或者节点数太多。解决把k调到1/sqrt(n)左右n 是节点数节点超过 50 个改用circular_layout或先做社区发现再分块画。另一个原因是node_size太大把边盖住了降到 200 试试。5.4 现象CSV 读进来节点数比预期少原因CSV 里有重复边NetworkX 的add_edge对已存在的边只更新属性不新增所以number_of_edges会比 CSV 行数少。解决这是正常行为如果想统计原始链路数在读 CSV 时单独用计数器累加不要依赖G.number_of_edges()。另外检查是否有空行或表头没跳过。5.5 现象模拟故障后原图也被改了原因直接G.remove_node()没有先copy()。解决养成习惯任何要修改图的模拟操作第一行先H G.copy()。copy()是深拷贝节点属性字典也会复制改 H 不影响 G。如果图特别大用G.copy(as_viewFalse)明确要可写副本。6. 把拓扑实验做成可复用的分析脚本前面五章跑通之后你手里已经有一堆零散代码。真正让这个实验值回票价的做法是把它封装成一个命令行工具输入 CSV 输出报告。我自己的习惯是建一个topo_analyzer.py用argparse接参数import argparse import networkx as nx import csv def load_topology(path): G nx.Graph() with open(path, encodingutf-8) as f: reader csv.reader(f) next(reader) for row in reader: G.add_edge(row[0], row[1], weightfloat(row[2])) return G def analyze(G): report {} report[nodes] G.number_of_nodes() report[edges] G.number_of_edges() report[connected] nx.is_connected(G) if report[connected]: report[diameter] nx.diameter(G) report[avg_shortest_path] nx.average_shortest_path_length(G) report[cut_nodes] list(nx.articulation_points(G)) return report if __name__ __main__: parser argparse.ArgumentParser(description网络拓扑分析工具) parser.add_argument(csv, help拓扑CSV文件路径) args parser.parse_args() G load_topology(args.csv) result analyze(G) for k, v in result.items(): print(f{k}: {v})逻辑说明load_topology把 CSV 读取独立出来方便换数据源。analyze返回字典包含节点数、边数、连通性、直径、平均最短路径和割点。参数说明nx.diameter只在连通图上有效所以先判断connected。average_shortest_path_length在节点多时计算慢超过 200 节点建议跳过或抽样。这个脚本跑通后你可以进一步加两个功能一是用matplotlib自动出图并存到指定目录二是把报告写成 JSON 方便其他系统消费。验证方法很简单拿一个你已知结构的网络比如宿舍三台路由器串一台交换机手工画出预期结果再跑脚本对比。如果割点识别和你手工分析一致说明整条链路是通的。最后说个我自己的教训早期做拓扑实验我总想一步到位画得漂亮结果在布局参数上耗了两天算法一个没跑。后来改成先保证is_connected和dijkstra_path输出正确可视化用最丑的circular_layout先出图确认数据没问题再调样式。这个顺序帮我省了大量返工时间。希望帮到你。本文还有配套的精品资源点击获取