一、计算机网络课程设计任务书
(1)设计题目
链路状态路由协议的仿真
(2)已知技术参数和设计要求
1.给出一个简单的网络路由拓扑结构,编程实现基于此拓扑图的链路状态动态路由算法。
1.1结点之间的连接关系固定;
1.2链路开销可以由用户设定。
2.链路状态算法的实现:
2.1 链路状态消息的交换;
2.2 网络拓扑的描述/构造;
2.3 利用 Dijkstra 算法计算路由;
2.4 输出路由表;
2.5 简单改变网络拓扑或路由代价,动态触发路由更新信息的交换,从而动态改变路由表的输出。
3.网络拓扑结构的描述(数据结构)拓扑结构利用文件存储。
4.用可视化界面进行程序演示;可图形显示网络的拓扑结构。
(3)设计内容与设计步骤
1.分析链路状态路由协议与 Dijkstra 算法;
2.熟悉线程间通信与同步机制/或进程间通信机制;
3.网络拓扑的数据结构定义及文件存储;
4.链路状态消息的交换;
5.Dijkstra 算法实现;
6.结点路由表的显示;
7.完成课程设计报告。
(4)设计工作与进度安排
1.熟悉链路状态协议/算法 4 小时
2.链路状态算法的实现方式分析 4 小时
3.链路状态算法实现框架结构设计 8 小时
4.数据结构定义:包括网络拓扑结构、链路状态消息、路由表等
4 小时
5.Dijkstra算法实现 12小时
6.课程设计报告 4 小时
二、概要设计
三、详细设计
整个程序分为两部分,分别是 课设.py 和 MODULE.py 。
课设.py为菜单功能,MODULE.py为各功能的函数。
我们在课设.py中import MODULE以便能够调用各函数。
(1) 菜单功能
相关代码:
# 课设.py
import MODULE
# 主函数
if __name__ == '__main__':
# 设置全局变量
global V # V表示节点的个数
global G
flag = 0
while flag == 0:
print("=============================================")
print(" 链路状态路由协议的仿真 ")
print("\t\t1、初始化数据\n\t\t2、存入文件")
print("\t\t3、使用链路状态协议\n\t\t4、读取文件内容")
print("\t\t5、数据传输路径\n\t\t6、更新路由(增加)")
print("\t\t7、更新路由(删除)\n\t\t8、打印路由表")
print("\t\t9.打印拓扑结构图\n\t\t10、退出")
print("=============================================")
botton = int(input("tip:请输入数字,否则系统报错!\n "
"请选择要进行的操作:"))
if botton == 1: # 输入数据
V = int(input("tip:只能输入数字,否则系统报错!!\n请输入节点数:"))
adj = [[] for i in range(V)] # 创建V个节点的图,并且节点间还没有连接
G, adj = MODULE.inputdata(adj, V)
MODULE.printGraph(adj, V)
elif botton == 2: # 写入文件
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
if len(adj) == 0:
print("当前内容为空,没有必要保存,请先初始化数据!")
else:
print("正在写入数据......")
print("数据写入成功!")
MODULE.inputfile(adj)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 3: # 打印路由表
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.Dijkstra(G, adj)
except NameError:
# 如果捕获到 NameError4
# 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 4:
adj, V, G = MODULE.printfile()
if V == -1:
flag = 1
elif botton == 5:
try:
# 尝试访问变量 G
_ = G
# 如果这里没有抛出异常,那么 G 存在
MODULE.router_path(G)
except NameError:
# 如果捕获到 NameError 异常,那么 G 不存在
print("您未进行初始化数据!")
elif botton == 6:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V, G = MODULE.add_e(adj, V, G)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 7:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V = MODULE.pop(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 8:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_after(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 9:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_p(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 10:
MODULE.flag = 1
print("操作结束!")
else:
print("输入有误!!")
代码描述:
利用while循环和if -elif判断语句来实现菜单功能,变量botton作为按钮来实现功能的选择,程序的开头我们设置了全局变量V、G来分别存储路由个数和图的数据(包括顶点和边数权重),在每个if -elif语句中我使用了try: except:语句来实现对adj变量或者G变量进行存在判断,以此避免程序异常终止,并对使用者进行友好的提示。
(2) 初始化数据
相关代码:
# 课设.py
if botton == 1: # 输入数据
V = int(input("tip:只能输入数字,否则系统报错!!\n请输入节点数:"))
adj = [[] for i in range(V)] # 创建V个节点的图,并且节点间还没有连接
G, adj = MODULE.inputdata(adj, V)
MODULE.printGraph(adj, V)
# MODULE.py
def inputdata(adj, V): # 初始化数据
# 创建一个简单的图,创建V个节点(0 --- V-1)
global G
G = nx.Graph()
# 改进直接使用 range(V) 添加节点
G.add_nodes_from(range(V))
Max = V * (V - 1) // 2
print("tip:边数大于0且小于等于{}!".format(Max))
num = int(input("边数:"))
while num > Max or num < 0:
print("边数的大小不符合规定!请重新输入!")
num = int(input("边数:"))
print("tip:请注意,你总共输入了{}个节点".format(V))
print("\t我们规定节点依次从0到{}命名".format(V - 1))
# 使用列表来存储边和权重,稍后再添加到图中
edges_with_weights = []
for i in range(int(num)):
O1 = int(input("输入节点一:"))
O2 = int(input("输入节点二:"))
E = int(input("输入权重:"))
jump = -1
# 检查节点是否有效
if O1 not in G or O2 not in G:
print("节点 {} 或 {} 不存在!".format(O1, O2))
continue # 跳过这次输入
# 增加一条边
adj = addEdge(adj, O1, O2, E, jump)
# 添加到列表中,稍后再添加到图中
edges_with_weights.append((O1, O2, E))
# 现在我们已经有了所有的边和权重,可以添加到图中并计算布局
G.add_weighted_edges_from(edges_with_weights)
pos = nx.circular_layout(G) # 在这里计算布局
# 绘图
nx.draw_networkx(G, pos, with_labels=True)
edge_labels = {(u, v): d['weight'] for u, v, d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
plt.show()
return G,adj
def printGraph(adj, V):
v, w = 0, 0
for u in range(len(adj)):
print("路由", adj[u][0][3], "的路由表")
print("-----------------------------------------------")
print("\t目的路由", "\t长度")
print("-----------------------------------------------")
for it in adj[u]:
v = it[0]
w = it[1]
print("\t", v, "\t\t", w)
print("------------------------------------------------")
print()
def addEdge(adj, u, v, wt, jump):
adj[u].append([v, wt, jump, u])
adj[v].append([u, wt, jump, v])
return adj
代码描述:
在课设.py中,我们先输入路由器的数量,即变量V的大小,然后生成adj并先后调用 MODULE.inputdata(adj, V)和MODULE.printGraph(adj, V),在MODULE.inputdata函数中,我们通过networkx库的nx.Graph()函数来创建图G,之后输入图G的数据,比如边数、各点之间的权重,在此处我们会对输入的数据进行判断,来检查输入是否合法,每次输入完一条边后,我们会调用adj = addEdge(adj, O1, O2, E, jump)来修改变量adj,变量adj存储的是图的信息,我们可以看成是三维矩阵或者三维列表。然后我们将每条边的信息存储起来edges_with_weights.append((O1, O2, E)),再通过networkx库中的函数进行绘图,并打印路由表,我们先统一设置下一跳为-1。
(3) 文件功能
相关代码:
# 课设.py
elif botton == 2: # 写入文件
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
if len(adj) == 0:
print("当前内容为空,没有必要保存,请先初始化数据!")
else:
print("正在写入数据......")
print("数据写入成功!")
MODULE.inputfile(adj)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 4:
adj, V, G = MODULE.printfile()
if V == -1:
flag = 1
# MODULE.py
def inputfile(adj):
F = open("data of Router1.pkl", 'wb')
pickle.dump(adj, F, 0)
def printfile():
file_path = 'data of Router1.pkl'
if not os.path.exists(file_path):
print("文件不存在:", file_path)
return None, -1, None
with open('data of Router1.pkl', 'rb') as File:
adj = pickle.load(File)
V = len(adj)
G = nx.Graph()
G.add_nodes_from(range(V))
# 添加边和权重
edges_with_weights = []
for i in range(V):
for j in range(len(adj[i])):
edges_with_weights.append((i, adj[i][j][0], adj[i][j][1]))
G.add_weighted_edges_from(edges_with_weights)
# 设置图的布局
pos = nx.circular_layout(G, center=[-1, 1])
# 绘制图
nx.draw(G, pos, with_labels=True)
# 绘制边的标签
edge_labels = {(u, v): w for u, v, w in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')
# 显示图
plt.axis('off') # 关闭坐标轴
plt.show()
return adj, V, G
代码描述:
写入文件
在课设.py中我们需要先对adj进行判断,检查其是否存在,若存在仍需检查len(adj)是否为0,若为0则无需进行保存,否则会调用MODULE.inputfile(adj)并将adj的内容存入data of Router1.pkl文件中。
读取文件
在课设.py,我们直接调用 adj, V, G = MODULE.printfile()函数.若文件不存在则赋值flag = 1
1.打开并加载 pickle 文件:
使用 with open('data of Router1.pkl', 'rb') as File: 语句以二进制读取模式打开 pickle 文件。
使用 pickle.load(File) 加载文件中的邻接表示到变量 adj 中。
2.初始化 NetworkX 图:
根据邻接表示的长度 V(即图中顶点的数量)创建一个空的 NetworkX 图 G。
使用 G.add_nodes_from(range(V)) 将从 0 到 V-1 的整数作为顶点添加到图中。
3.添加边和权重:
初始化一个空列表 edges_with_weights 用于存储带权重的边。
遍历邻接表示 adj 中的每一行(代表一个顶点)和每一列(代表与该顶点相连的其他顶点)。
对于每一对相连的顶点 (i, j),从 adj[i][j] 中提取顶点 j 和其对应的权重,并将它们作为 (i, j, weight) 三元组添加到 edges_with_weights 列表中。
使用 G.add_weighted_edges_from(edges_with_weights) 将带权重的边添加到 NetworkX 图 G 中。
4.设置图的布局:
使用 NetworkX 的 circular_layout 函数为图 G 设置一个圆形的布局,并将布局信息存储在 pos 变量中。
5.绘制图:
使用 NetworkX 的 draw 函数根据 pos 布局绘制图 G。
设置 with_labels=True 以在图中显示顶点标签。
6.绘制边的标签:
使用字典解析从 G.edges(data=True) 获取的边和权重数据中提取出边的标签,并将它们存储在 edge_labels 字典中。
使用 NetworkX 的 draw_networkx_edge_labels 函数绘制边的标签,并设置字体颜色为红色。
7.显示图:
使用 Matplotlib(NetworkX 绘图功能的依赖库)的 axis('off') 函数关闭坐标轴。
使用 show 函数显示绘制好的图。
8.返回值:
函数返回加载的邻接表示 adj、顶点数量 V 和 NetworkX 图 G。
(4)更新路由功能
相关代码;
# 课设.py
elif botton == 6:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V, G = MODULE.add_e(adj, V, G)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 7:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V = MODULE.pop(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
# MODULE.py
def add_e(adj, V, G):
op = int(input("是否添加新的路由器\n若是则输入'1',否则输入除'1'外的其他的符号"))
if op ==1 :
r = int(V - 1)
G.add_node(r)
adj.append([])
V = V + 1
print("已为您添加了新路由器{}".format(V - 1))
print("tip:若选择新增路由器,则在添加边的信息时,必须为新路由器分配新边!!")
num = int(input("请输入要添加的边数"))
print("tip:请注意输入的边数!!不能太大!")
while num > 0:
O1 = int(input("输入节点一:"))
O2 = int(input("输入节点二:"))
E = int(input("输入权重:"))
jump = -1
adj = addEdge(adj, O1, O2, E, jump)
print("添加成功!")
num = num - 1
return adj, V, G
def pop(adj, V):
print("tip:在此处提醒:删除边或者删除路由器之后,请保存文件并且重新读取文件来更新数据!!!!\n"
"并且我们只允许删除路由器{}".format(V - 1))
b = int(input("1、要删除边\n 2、删除路由器\n请输入:"))
if b == 1:
u = int(input("请输入要删除对应边的其中一端路由器"))
v = int(input("请输入要删除对应边的另一端路由器"))
e = int(input("请输入对应边的权值"))
jump = -1
adj[u].remove([v, e, jump, u])
adj[v].remove([u, e, jump, v])
if b == 2:
global r
out = []
r = int(V - 1)
for i in range(len(adj)):
for j in range(len(adj[i])): # 找到adj中对应边的权值
if adj[i][j][0] == r:
out.append([r, adj[i][j][1], adj[i][j][2], adj[i][j][3]])
if len(out) != 0:
adj[i].remove(out[0])
out = []
# print(adj)
for i in range(len(adj)):
try:
if adj[i][0][3] == r:
del adj[i]
except:
continue
V = V - 1
# print(adj)
print("路由器{}删除成功!".format(V))
return adj, V
代码描述:
增加路由、边
在课设.py中我们需要对adj进行判断,检查其是否存在,然后调用adj, V, G = MODULE.add_e(adj, V, G)。
1.询问用户是否添加新的路由器:
显示一个提示,询问用户是否想要添加新的路由器。
如果用户输入 1,则执行以下步骤:
设定新路由器的索引为当前顶点数量 V 减一(即最后一个顶点的下一个索引)。
使用 G.add_node(r) 将新路由器(即新顶点)添加到图 G 中。
在邻接表示 adj 中添加一个新的空列表来代表新路由器的邻居。
更新顶点数量 V。
显示一个消息,通知用户已成功添加新路由器。
无论用户是否选择添加新路由器,都会显示一个提示,提醒用户在添加边的信息时需要为新路由器分配新边(如果添加了新路由器的话)。
2.询问用户要添加的边数:
显示一个提示,让用户输入想要添加的边数,并将输入的值存储在变量 num 中。
显示另一个提示,提醒用户注意输入的边数。
3.添加边:
使用一个 while 循环,循环次数由用户输入的边数 num 决定。
在每次循环中,提示用户输入两个顶点(O1 和 O2)的索引和边的权重 E。
调用一个名为 addEdge 的函数来更新邻接表示 adj,添加从 O1 到 O2 的带权重边。该函数还接受一个 jump 参数,它被设置为 -1 。
显示一个消息,通知用户已成功添加边。
减少剩余要添加的边数 num。
4.返回值:
函数返回更新后的邻接表示 adj、顶点数量 V 和 NetworkX 图 G。
删除路由和边
在课设.py中我们需要对adj进行判断,检查其是否存在,然后调用adj, V = MODULE.pop(adj, V)。
1.提醒用户:
函数首先打印一个提醒,告知用户删除操作后需要保存并重新读取文件来更新数据。
提醒用户只能删除最后一个路由器(索引为V - 1的路由器)。
2.获取用户选择:
询问用户是要删除边(输入1)还是要删除路由器(输入2)。
3.删除边(如果用户选择1):
获取用户要删除边的两个端点(路由器)的索引u和v,以及该边的权值e。
邻接表adj中的每个条目都是一个包含四个元素的列表(即[v, e, jump, u])
尝试从adj[u]和adj[v]中删除这些条目。
4.删除路由器(如果用户选择2):
设定要删除的路由器索引为r,它等于V - 1(即最后一个路由器)。
遍历邻接表adj,寻找与路由器r相连的所有边,并将它们存储在out列表中。
遍历adj,并尝试删除第一个与r相关的条目。但这里使用了adj[i][0][3] == r作为条件,这是基于假设每个条目包含四个元素,并且第四个元素是原始顶点的索引。
更新顶点数量V。
[v, e, jump, u]解释一下就是[目的,权重,下一跳,起点]。
我们简洁的来说就是,先删除以最后一个路由器为目的路由的边,然后删除以最后一个路由器为起点的边。
(5) 使用链路状态路由协议
相关代码:
# 课设.py
elif botton == 3: # 打印路由表
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.Dijkstra(G, adj)
except NameError:
# 如果捕获到 NameError4
# 异常,那么 adj 不存在
print("您未进行初始化数据!")
# MODULE.py
def Dijkstra(G, adj):
Q = len(adj)
input_n = []
for i in range(len(adj)):
input_n.append(adj[i][0][3])
adj = [[] for i in range(Q)]
for i in range(Q):
for j in range(Q):
if j in input_n:
try:
if nx.has_path(G, i, j):
path = nx.dijkstra_path(G, source=i, target=j, weight='weight')
distance = nx.dijkstra_path_length(G, source=i, target=j, weight="weight")
adj[i].append([j, distance, path[1], i])
except:
continue
print_after(adj, Q)
def print_after(adj, V):
v, w = 0, 0
for u in range(len(adj)):
try:
print("路由", adj[u][0][3], "的路由表")
print("-----------------------------------------------")
print("\t目的路由", "\t长度", "\t下一跳")
print("-----------------------------------------------")
for it in adj[u]:
v = it[0] # 目的路由
w = it[1] # 长度
e = it[2] # 下一跳
print("\t", v, "\t\t", w, "\t", e)
print("------------------------------------------------")
print()
except:
continue
代码描述:
在课设.py中,我们先对adj进行判断,如果adj存在则调用 MODULE.Dijkstra(G, adj),
1.初始化:
确定图的顶点数量Q,基于输入邻接表adj的长度。
创建一个新的空邻接表adj(在这个函数中我们没有return语句去返回adj变量到课设.py,所以不用担心覆盖问题)。
创建一个列表input_n,用于存储邻接表中每个顶点。
2.遍历顶点对并计算最短路径:
对于每一对顶点(i和j),其中j是input_n列表中的一个顶点(即它存在于原始邻接表中):
使用NetworkX的has_path函数检查图G中是否存在从i到j的路径。
如果存在路径,则使用dijkstra_path函数计算从i到j的最短路径,并使用dijkstra_path_length函数计算该路径的长度(即距离)。
将计算出的最短路径的信息(目标顶点j、距离、路径中的下一个顶点path[1]和起始顶点i)添加到新的邻接表adj中,对应于起始顶点i的列表。
3.错误处理:
如果在计算最短路径或路径长度时发生任何异常(如没有从i到j的路径),则跳过当前迭代并继续下一个顶点对。
然后我们调用print_after(adj,Q)函数对adj进行打印。
(6)打印路由表
相关代码:
# 课设.py
elif botton == 8:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_after(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
# MODULE.py
def print_after(adj, V):
v, w = 0, 0
for u in range(len(adj)):
try:
print("路由", adj[u][0][3], "的路由表")
print("-----------------------------------------------")
print("\t目的路由", "\t长度", "\t下一跳")
print("-----------------------------------------------")
for it in adj[u]:
v = it[0] # 目的路由
w = it[1] # 长度
e = it[2] # 下一跳
print("\t", v, "\t\t", w, "\t", e)
print("------------------------------------------------")
print()
except:
continue
代码描述:
在课设.py中,我们先对adj进行检查,如果adj存在,则调用MODULE.print_after(adj, V)对adj进行打印。
(7)打印路由拓扑图结构图
相关代码:
# 课设.py
elif botton == 9:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_p(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
# MODULE.py
def print_p(adj, V):
V = len(adj)
G = nx.Graph()
G.add_nodes_from(range(V))
# 添加边和权重
edges_with_weights = []
for i in range(V):
for j in range(len(adj[i])):
edges_with_weights.append((i, adj[i][j][0], adj[i][j][1]))
G.add_weighted_edges_from(edges_with_weights)
# 设置图的布局
pos = nx.circular_layout(G, center=[-1, 1])
# 绘制图
nx.draw(G, pos, with_labels=True)
# 绘制边的标签
edge_labels = {(u, v): w for u, v, w in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')
# 显示图
plt.axis('off') # 关闭坐标轴
plt.show()
代码描述:
在课设.py中,我们先对adj进行检擦,判断其是否存在,若存在则调用MODULE.print_p(adj, V)
1.初始化:
根据输入的邻接表adj的长度确定图中顶点的数量V创建一个空的NetworkX无向图G。
使用range(V)向图中添加V个顶点。
2.添加边和权重:
初始化一个空列表edges_with_weights,用于存储带有权重的边。
遍历邻接表adj中的每个顶点(索引为i)。
对于与顶点i相连的每条边(索引为j),从邻接表中提取目标顶点adj[i][j][0]、边的权重adj[i][j][1],并将它们与起始顶点i一起打包为一个三元组(i, adj[i][j][0], adj[i][j][1]),然后添加到edges_with_weights列表中。
使用G.add_weighted_edges_from(edges_with_weights)方法将带有权重的边添加到图G中。
3.设置图的布局:
使用NetworkX的circular_layout函数为图G设置圆形布局,并将中心设置为[-1, 1]。这将确定节点在图中的位置。
4.绘制图:
使用NetworkX的draw函数和之前设置的布局pos来绘制图G,并显示节点的标签。
5.绘制边的标签:
创建一个字典edge_labels,其键是图中的边(表示为顶点对的元组),值是边的权重。这个字典是通过遍历图G中的边(使用G.edges(data=True))并提取边的权重来构建的。
使用NetworkX的draw_networkx_edge_labels函数将边的权重作为标签绘制在图上,并设置字体颜色为红色。
6.显示图:
使用matplotlib库的axis('off')函数关闭坐标轴,以便仅显示图而不显示坐标轴。
使用matplotlib库的show函数显示绘制好的图
(8)数据传输路径(即最短路径)
相关代码:
# 课设.py
elif botton == 5:
try:
# 尝试访问变量 G
_ = G
# 如果这里没有抛出异常,那么 G 存在
MODULE.router_path(G)
except NameError:
# 如果捕获到 NameError 异常,那么 G 不存在
print("您未进行初始化数据!")
# MODULE.py
def router_path(G):
print("tip:注意!输入范例'1 2',代表路由器1和路由器2,中间是空格!!")
u, v = map(int, input("请输入两个路由器的序号:").split())
print('Dijkstra方法寻找最短路径:')
try:
path = nx.dijkstra_path(G, source=u, target=v,
weight='weight') # 注意函数名是 dijkstra_path 而不是 dijkstra_path(拼写错误)
except nx.NetworkXNoPath:
print("从节点", u, "到节点", v, "没有路径。")
return
# 原始图的布局
pos = nx.spring_layout(G) # 使用spring_layout可能会减少重叠,因为它尝试分散节点
# 绘制原始图
nx.draw(G, pos, with_labels=True, node_color='white', edge_color='gray', alpha=0.5, node_size=400)
# 将zip对象转换为列表,以便我们可以使用len()函数
edgelist = list(zip(path[:-1], path[1:]))
# 在原始图上高亮显示最短路径
nx.draw_networkx_edges(G, pos, edgelist=edgelist, edge_color='red', width=2)
# 绘制边标签
# 这里我们为最短路径的边单独设置标签,以避免过多标签导致重叠
edge_labels = {(u, v): G[u][v]['weight'] for u, v in edgelist}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='black', font_size=8, font_weight='bold')
# 显示节点和最短路径
print('节点', u, '到', v, '的最短路径:', path)
# 计算并显示最短距离
distance = nx.dijkstra_path_length(G, source=u, target=v, weight='weight')
print('节点', u, '到', v, '的距离为:', distance)
# 显示图形
plt.show() # 使用matplotlib的pyplot的show函数
代码描述:
在课设.py中我们先对adj进行检查,若adj存在则调用MODULE.router_path(G)。
1.用户输入提示:
打印提示信息,告知用户如何输入两个路由器的序号。
2.用户输入处理:
接收用户输入的路由器序号字符串,并使用split方法将其按空格分割成两个部分。
使用map函数和int类型转换将分割后的字符串转换为整数,并将这两个整数分别赋值给变量u和v。
3.最短路径查找:
尝试使用NetworkX库的dijkstra_path函数来查找从节点u到节点v的最短路径。
如果图中不存在从u到v的路径,则捕获NetworkXNoPath异常,并打印相应的错误信息,然后函数返回。
4.图的布局设置:
使用NetworkX的spring_layout函数为图G设置布局,以减少节点间的重叠。
5.绘制原始图:
使用NetworkX的draw函数绘制原始图,并设置节点的颜色、边缘颜色、透明度、节点大小等属性。
6.最短路径的高亮显示:
从计算出的最短路径path中提取边对(即连续节点对),并将它们存储在一个列表edgelist中。
使用draw_networkx_edges函数在原始图上高亮显示这些边,设置边缘颜色为红色并增加宽度。
7.绘制边标签:
创建一个字典edge_labels,其中键是最短路径中的边对,值是这些边在图中对应的权重。
使用draw_networkx_edge_labels函数在图上绘制这些边的权重标签,以避免在原始图上显示过多的标签。
8.输出最短路径和距离:
打印出从节点u到节点v的最短路径。
使用NetworkX的dijkstra_path_length函数计算从u到v的最短距离,并打印出来。
9.显示图形:
使用matplotlib的pyplot模块的show函数显示绘制好的图形。
三、全部代码
import MODULE
# 主函数
if __name__ == '__main__':
# 设置全局变量
global V # V表示节点的个数
global G
flag = 0
while flag == 0:
print("=============================================")
print(" 链路状态路由协议的仿真 ")
print("\t\t1、初始化数据\n\t\t2、存入文件")
print("\t\t3、使用链路状态协议\n\t\t4、读取文件内容")
print("\t\t5、数据传输路径\n\t\t6、更新路由(增加)")
print("\t\t7、更新路由(删除)\n\t\t8、打印路由表")
print("\t\t9.打印拓扑结构图\n\t\t10、退出")
print("=============================================")
botton = int(input("tip:请输入数字,否则系统报错!\n "
"请选择要进行的操作:"))
if botton == 1: # 输入数据
V = int(input("tip:只能输入数字,否则系统报错!!\n请输入节点数:"))
adj = [[] for i in range(V)] # 创建V个节点的图,并且节点间还没有连接
G, adj = MODULE.inputdata(adj, V)
MODULE.printGraph(adj, V)
elif botton == 2: # 写入文件
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
if len(adj) == 0:
print("当前内容为空,没有必要保存,请先初始化数据!")
else:
print("正在写入数据......")
print("数据写入成功!")
MODULE.inputfile(adj)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 3: # 打印路由表
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.Dijkstra(G, adj)
except NameError:
# 如果捕获到 NameError4
# 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 4:
adj, V, G = MODULE.printfile()
if V == -1:
flag = 1
elif botton == 5:
try:
# 尝试访问变量 G
_ = G
# 如果这里没有抛出异常,那么 G 存在
MODULE.router_path(G)
except NameError:
# 如果捕获到 NameError 异常,那么 G 不存在
print("您未进行初始化数据!")
elif botton == 6:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V, G = MODULE.add_e(adj, V, G)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 7:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
adj, V = MODULE.pop(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 8:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_after(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 9:
try:
# 尝试访问变量 adj
_ = adj
# 如果这里没有抛出异常,那么 adj 存在
MODULE.print_p(adj, V)
except NameError:
# 如果捕获到 NameError 异常,那么 adj 不存在
print("您未进行初始化数据!")
elif botton == 10:
MODULE.flag = 1
print("操作结束!")
else:
print("输入有误!!")
# MODULE
from matplotlib import pyplot as plt
import networkx as nx
import pickle
import pylab
'''
import matplotlib
matplotlib.use('TkAgg') # 或 'Qt5Agg', 'GTK3Agg' 等
'''
# 配置Matplotlib库中的绘图参数,特别是与字体和字符显示相关的设置
plt.rcParams['font.family'] = ['Arial Unicode MS', 'Microsoft YaHei', 'SimHei', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False
# 增加一条边
def addEdge(adj, u, v, wt, jump):
adj[u].append([v, wt, jump, u])
adj[v].append([u, wt, jump, v])
return adj
# Print adjacency list representation of graph
def printGraph(adj, V):
v, w = 0, 0
for u in range(len(adj)):
print("路由", adj[u][0][3], "的路由表")
print("-----------------------------------------------")
print("\t目的路由", "\t长度")
print("-----------------------------------------------")
for it in adj[u]:
v = it[0]
w = it[1]
print("\t", v, "\t\t", w)
print("------------------------------------------------")
print()
def print_after(adj, V):
v, w = 0, 0
for u in range(len(adj)):
try:
print("路由", adj[u][0][3], "的路由表")
print("-----------------------------------------------")
print("\t目的路由", "\t长度", "\t下一跳")
print("-----------------------------------------------")
for it in adj[u]:
v = it[0] # 目的路由
w = it[1] # 长度
e = it[2] # 下一跳
print("\t", v, "\t\t", w, "\t", e)
print("------------------------------------------------")
print()
except:
continue
def inputdata(adj, V): # 初始化数据
# 创建一个简单的图,创建V个节点(0 --- V-1)
global G
G = nx.Graph()
# 改进直接使用 range(V) 添加节点
G.add_nodes_from(range(V))
Max = V * (V - 1) // 2
print("tip:边数大于0且小于等于{}!".format(Max))
num = int(input("边数:"))
while num > Max or num < 0:
print("边数的大小不符合规定!请重新输入!")
num = int(input("边数:"))
print("tip:请注意,你总共输入了{}个节点".format(V))
print("\t我们规定节点依次从0到{}命名".format(V - 1))
# 使用列表来存储边和权重,稍后再添加到图中
edges_with_weights = []
for i in range(int(num)):
O1 = int(input("输入节点一:"))
O2 = int(input("输入节点二:"))
E = int(input("输入权重:"))
jump = -1
# 检查节点是否有效
if O1 not in G or O2 not in G:
print("节点 {} 或 {} 不存在!".format(O1, O2))
continue # 跳过这次输入
# 增加一条边
adj = addEdge(adj, O1, O2, E, jump)
# 添加到列表中,稍后再添加到图中
edges_with_weights.append((O1, O2, E))
# 现在我们已经有了所有的边和权重,可以添加到图中并计算布局
G.add_weighted_edges_from(edges_with_weights)
pos = nx.circular_layout(G) # 在这里计算布局
# 绘图
nx.draw_networkx(G, pos, with_labels=True)
edge_labels = {(u, v): d['weight'] for u, v, d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
plt.show()
return G,adj
'''
def printfile():
File = open('data of Router1.pkl', 'rb')
global adj
adj = pickle.load(File)
global V
V = len(adj)
print_after(adj, len(adj))
global G
G = nx.Graph()
input_n = []
for i in range(V):
input_n.append(i)
G.add_nodes_from(input_n)
pos = nx.circular_layout(G, center=[-1, 1])
d = {}
tmp = {}
for i in range(len(adj)):
for j in range(len(adj[i])):
G.add_weighted_edges_from([(i, adj[i][j][0], adj[i][j][1])])
nx.draw_networkx(G, pos)
d = {(i, adj[i][j][0]): adj[i][j][1]}
tmp.update(d)
nx.draw_networkx_edge_labels(
G, pos,
edge_labels={(i, adj[i][j][0]): adj[i][j][1]},
font_color='red'
)
nx.draw_networkx_edge_labels(
G, pos,
edge_labels=tmp,
font_color='blue'
)
plt.show()
return adj, V, G
'''
def printfile():
file_path = 'data of Router1.pkl'
if not os.path.exists(file_path):
print("文件不存在:", file_path)
return None, -1, None
with open('data of Router1.pkl', 'rb') as File:
adj = pickle.load(File)
V = len(adj)
G = nx.Graph()
G.add_nodes_from(range(V))
# 添加边和权重
edges_with_weights = []
for i in range(V):
for j in range(len(adj[i])):
edges_with_weights.append((i, adj[i][j][0], adj[i][j][1]))
G.add_weighted_edges_from(edges_with_weights)
# 设置图的布局
pos = nx.circular_layout(G, center=[-1, 1])
# 绘制图
nx.draw(G, pos, with_labels=True)
# 绘制边的标签
edge_labels = {(u, v): w for u, v, w in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')
# 显示图
plt.axis('off') # 关闭坐标轴
plt.show()
return adj, V, G
def Dijkstra(G, adj):
Q = len(adj)
input_n = []
for i in range(len(adj)):
input_n.append(adj[i][0][3])
adj = [[] for i in range(Q)]
# print(Q)
# print(adj)
# path1 = nx.dijkstra_path(G, source=1, target=2)
# distance1 = nx.dijkstra_path_length(G, source=1, target=2)
# print(path1)
# print(distance1)
for i in range(Q):
for j in range(Q):
if j in input_n:
try:
if nx.has_path(G, i, j):
path = nx.dijkstra_path(G, source=i, target=j, weight='weight')
distance = nx.dijkstra_path_length(G, source=i, target=j, weight="weight")
adj[i].append([j, distance, path[1], i])
except:
continue
# print(Q)
# print(adj)
print_after(adj, Q)
def router_path(G):
'''
print("tip:注意!输入范例'1 2',代表路由器1和路由器2,中间是空格!!")
u, v = map(int, input("请输入两个路由器的序号:").split())
print('Dijkstra方法寻找最短路径:')
try:
path = nx.dijkstra_path(G, source=u, target=v, weight='weight')
except nx.NetworkXNoPath:
print("从节点", u, "到节点", v, "没有路径。")
return
pos = nx.circular_layout(G, center=[-1, 1])
# print(pos)
# 创建一个新的图来仅显示最短路径
R = nx.DiGraph()
edge_labels = {}
for i in range(len(path) - 1):
# 获取边的权重
weight = G[path[i]][path[i + 1]].get('weight', 1) # 假设默认权重为1
R.add_edge(path[i], path[i + 1], weight=weight)
edge_labels[(path[i], path[i + 1])] = weight
# 画图
nx.draw(R, pos, with_labels=True, node_color='white', edge_color='green', node_size=400, alpha=0.5)
nx.draw_networkx_edge_labels(R, pos, edge_labels=edge_labels, font_color='black')
print('节点', u, '到', v, '的最短路径:', path)
print('Dijkstra方法寻找最短距离:')
distance = nx.dijkstra_path_length(G, source=u, target=v, weight='weight')
print('节点', u, '到', v, '的距离为:', distance)
pylab.title("最短路径图", fontsize=15)
pylab.show()
'''
'''
print("tip:注意!输入范例'1 2',代表路由器1和路由器2,中间是空格!!")
u, v = map(int, input("请输入两个路由器的序号:").split())
print('Dijkstra方法寻找最短路径:')
try:
path = nx.dijkstra_path(G, source=u, target=v, weight='weight')
except nx.NetworkXNoPath:
print("从节点", u, "到节点", v, "没有路径。")
return
# 原始图的布局
pos = nx.circular_layout(G)
# 绘制原始图
nx.draw(G, pos, with_labels=True, node_color='white', edge_color='gray', alpha=0.5, node_size=400)
# 将zip对象转换为列表,以便我们可以使用len()函数
edgelist = list(zip(path[:-1], path[1:]))
# 在原始图上高亮显示最短路径
nx.draw_networkx_edges(G, pos, edgelist=edgelist, edge_color='red', width=2)
# 绘制边标签(可选)
edge_labels = {(u, v): G[u][v]['weight'] if 'weight' in G[u][v] else 1 for u, v in G.edges()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='black')
# 显示节点和最短路径
print('节点', u, '到', v, '的最短路径:', path)
# 计算并显示最短距离
distance = nx.dijkstra_path_length(G, source=u, target=v, weight='weight')
print('节点', u, '到', v, '的距离为:', distance)
pylab.show()
'''
print("tip:注意!输入范例'1 2',代表路由器1和路由器2,中间是空格!!")
u, v = map(int, input("请输入两个路由器的序号:").split())
print('Dijkstra方法寻找最短路径:')
try:
path = nx.dijkstra_path(G, source=u, target=v,
weight='weight') # 注意函数名是 dijkstra_path 而不是 dijkstra_path(拼写错误)
except nx.NetworkXNoPath:
print("从节点", u, "到节点", v, "没有路径。")
return
# 原始图的布局
pos = nx.spring_layout(G) # 使用spring_layout可能会减少重叠,因为它尝试分散节点
# 绘制原始图
nx.draw(G, pos, with_labels=True, node_color='white', edge_color='gray', alpha=0.5, node_size=400)
# 将zip对象转换为列表,以便我们可以使用len()函数
edgelist = list(zip(path[:-1], path[1:]))
# 在原始图上高亮显示最短路径
nx.draw_networkx_edges(G, pos, edgelist=edgelist, edge_color='red', width=2)
# 绘制边标签
# 这里我们为最短路径的边单独设置标签,以避免过多标签导致重叠
edge_labels = {(u, v): G[u][v]['weight'] for u, v in edgelist}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='black', font_size=8, font_weight='bold')
# 显示节点和最短路径
print('节点', u, '到', v, '的最短路径:', path)
# 计算并显示最短距离
distance = nx.dijkstra_path_length(G, source=u, target=v, weight='weight')
print('节点', u, '到', v, '的距离为:', distance)
# 显示图形
plt.show() # 使用matplotlib的pyplot的show函数
def print_p(adj, V):
'''
global G
G = nx.Graph()
input_n = []
for i in range(len(adj)):
input_n.append(adj[i][0][3])
G.add_nodes_from(input_n)
pos = nx.circular_layout(G, center=[-1, 1])
d = {}
tmp = {}
for i in range(len(adj)):
for j in range(len(adj[i])):
try:
G.add_weighted_edges_from([(adj[i][j][3], adj[i][j][0], adj[i][j][1])])
nx.draw_networkx(G, pos)
d = {(adj[i][j][3], adj[i][j][0]): adj[i][j][1]}
tmp.update(d)
nx.draw_networkx_edge_labels(
G, pos,
edge_labels={(adj[i][j][3], adj[i][j][0]): adj[i][j][1]},
font_color='red'
)
nx.draw_networkx_edge_labels(
G, pos,
edge_labels=tmp,
font_color='blue'
)
except:
continue
plt.show()
'''
# 与从文件中读取后进行画图的操作一样,从上面改进而来
V = len(adj)
G = nx.Graph()
G.add_nodes_from(range(V))
# 添加边和权重
edges_with_weights = []
for i in range(V):
for j in range(len(adj[i])):
edges_with_weights.append((i, adj[i][j][0], adj[i][j][1]))
G.add_weighted_edges_from(edges_with_weights)
# 设置图的布局
pos = nx.circular_layout(G, center=[-1, 1])
# 绘制图
nx.draw(G, pos, with_labels=True)
# 绘制边的标签
edge_labels = {(u, v): w for u, v, w in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')
# 显示图
plt.axis('off') # 关闭坐标轴
plt.show()
def add_e(adj, V, G):
pos = nx.spring_layout(G)
op = int(input("是否添加新的路由器\n若是则输入'1',否则输入除'1'外的其他的符号"))
if op ==1 :
r = int(V - 1)
G.add_node(r)
adj.append([])
V = V + 1
print("已为您添加了新路由器{}".format(V - 1))
print("tip:若选择新增路由器,则在添加边的信息时,必须为新路由器分配新边!!")
num = int(input("请输入要添加的边数"))
print("tip:请注意输入的边数!!不能太大!")
while num > 0:
O1 = int(input("输入节点一:"))
O2 = int(input("输入节点二:"))
E = int(input("输入权重:"))
jump = -1
adj = addEdge(adj, O1, O2, E, jump)
print("添加成功!")
num = num - 1
return adj, V, G
def inputfile(adj):
F = open("data of Router1.pkl", 'wb')
pickle.dump(adj, F, 0)
def pop(adj, V):
print("tip:在此处提醒:删除边或者删除路由器之后,请保存文件并且重新读取文件来更新数据!!!!\n"
"并且我们只允许删除路由器{}".format(V - 1))
b = int(input("1、要删除边\n 2、删除路由器\n请输入:"))
if b == 1:
u = int(input("请输入要删除对应边的其中一端路由器"))
v = int(input("请输入要删除对应边的另一端路由器"))
e = int(input("请输入对应边的权值"))
jump = -1
adj[u].remove([v, e, jump, u])
adj[v].remove([u, e, jump, v])
if b == 2:
global r
out = []
r = int(V - 1)
for i in range(len(adj)):
for j in range(len(adj[i])): # 找到adj中对应边的权值
if adj[i][j][0] == r:
out.append([r, adj[i][j][1], adj[i][j][2], adj[i][j][3]])
if len(out) != 0:
adj[i].remove(out[0])
out = []
# print(adj)
for i in range(len(adj)):
try:
if adj[i][0][3] == r:
del adj[i]
except:
continue
V = V - 1
# print(adj)
print("路由器{}删除成功!".format(V))
return adj, V