广工计网课设链路状态路由协议的仿真

 

 一、计算机网络课程设计任务书


(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 小时

 

二、概要设计

ac5b1ecf7bbc4040bf6c75edb666c2bd.png

三、详细设计

        整个程序分为两部分,分别是 课设.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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值