算法主要分为两块:GUI界面的实现和向量算法的实现
使用了多线程来让所有路由器一起工作,设置了每个路由器接受更新路由表的时间为1s。
由用户输入每个路由的初始路由表:路由名称和目的网络,距离默认为1(支持1-16的输入,大于16或小于1会提示错误),下一跳默认为空不填(支持填写)。
一. GUI界面的实现:tkinter
实现了如下界面,包括了路由信息的输入,模拟路由器故障以及保存信息三个模块,使用了Button,Label,Entry,Text,messageBox,filedialog等控件。
代码片段:
#GUI界面
root = Tk()
root.title('基于距离向量算法的路由协议的实现')
#左边的输入框
m_l = PanedWindow(showhandle = True, sashrelief = SUNKEN)
m_l.pack(fill = BOTH, expand = 1, padx = 10, pady = 10)
frame_l = LabelFrame(m_l, text = '输入路由信息:', font = 18, padx = 5, pady = 5)
frame_l.pack(padx = 10, pady = 10)
Label(frame_l, text = '路由器名称:', font = 16).grid(row = 0, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '目的网络:', font = 16).grid(row = 1, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '距离:', font = 16).grid(row = 2, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '下一跳:', font = 16).grid(row = 3, column = 0, sticky = W, pady = 5)
e_name = Entry(frame_l, justify = CENTER)
e_name.grid(row = 0, column = 1)
e_net_tar = Entry(frame_l, justify = CENTER)
e_net_tar.grid(row = 1, column = 1)
e_distance = Entry(frame_l, justify = CENTER)
e_distance.grid(row = 2, column = 1)
e_next_ = Entry(frame_l, justify = CENTER)
e_next_.grid(row = 3, column = 1)
b_add = Button(frame_l, text = '添加', font = 18, command = lambda : add_callback(tables), padx = 20, pady = 5)
b_add.grid(row = 4, column = 0, pady = 5)
b_start = Button(frame_l, text = '更新', font = 18, command = lambda : update_callback(tables,table_new), padx = 20, pady = 5)
b_start.grid(row = 4, column = 1, pady = 5)
Label(frame_l, text = '', font = 16).grid(row = 5, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '模拟网络故障:', font = 18).grid(row = 6, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '故障路由器名称:', font = 16).grid(row = 7, column = 0, sticky = W, pady = 5)
e_name_w = Entry(frame_l, justify = CENTER)
e_name_w.grid(row = 7, column = 1)
b_w = Button(frame_l, text = '故障', font = 18, command = lambda : wrong_callback(tables), padx = 20, pady = 5)
b_w.grid(row = 8, column = 0, pady = 5, columnspan = 2)
Label(frame_l, text = '', font = 16).grid(row = 9, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '保存信息:', font = 18).grid(row = 10, column = 0, sticky = W, pady = 5)
b_save_info = Button(frame_l, text = '保存路由表', font = 16, command = save_info_callback, padx = 10, pady = 5)
b_save_info.grid(row = 11, column = 0, pady = 5)
b_save_log = Button(frame_l, text = '保存日志', font = 16, command = save_log_callback, padx = 10, pady = 5)
b_save_log.grid(row = 11, column = 1, pady = 5)
#右边的信息和log
m_r = PanedWindow(orient = VERTICAL, showhandle = True, sashrelief = SUNKEN)
frame1 = LabelFrame(m_r, text = '路由表信息:', font = 16, padx = 5, pady = 5)
frame1.pack(padx = 10, pady = 10)
sb1 = Scrollbar(frame1)
sb1.pack(side = RIGHT, fill = Y)
t1 = Text(frame1, width = 60, height = 20, font = 16, state = DISABLED, yscrollcommand = sb1.set)
t1.pack(side = LEFT, fill = BOTH)
sb1.config(command = t1.yview)
m_r.add(frame1)
frame2 = LabelFrame(m_r, text = '日志信息:', font = 16, padx = 5, pady = 5)
frame2.pack(padx = 10, pady = 10)
sb2 = Scrollbar(frame2)
sb2.pack(side = RIGHT, fill = Y)
t2 = Text(frame2, width = 60, height = 15, font = 16, state = DISABLED, yscrollcommand = sb2.set)
t2.pack(side = LEFT, fill = BOTH)
sb2.config(command = t2.yview)
m_r.add(frame2)
m_l.add(frame_l)
m_l.add(m_r)
mainloop()
二.函数部分的实现
分为两个部分:实现该协议的函数和控件的callback函数
实现的主要函数的说明
add函数:用户输入一个路由信息,便向路由表的集合中添加一组数据
send函数:路由器每次发送自己的路由表的时候调用的函数
update函数:路由器每次更新路由表调用的函数
threads函数:为每个路由器的发送和更新路由表分配线程
控件的callback函数说明
(1)add_callback函数:点击添加按钮时调用的函数,在函数中调用了 add函数
(2)update_callback函数:用户点击更新按钮时调用的函数,在函数中调用了threads函数
(3)wrong_callback函数:用户点击故障按钮时的调用的函数,在函数中将路由表的集合中的故障路由的表项去掉
(4)save_info_callback&save_log_callbac函数:用户点击保存路由表/保存日志按钮时调用的函数,用于将信息以文本形式输出
三. 主要算法思想
update函数:这里实现了路由表的更新操作。假设每个路由器不知道相邻路由器的名称,只知道相邻的网络,则路由器发送路由表是以广播的形式,我用tables这个列表储存所有更新路由表的集合。
(1)首先需要找出相邻的路由,并从他们的路由表中得到更新表
先从自己的路由表中得到相邻网络的列表,存储在tar中。
(2)然后将tables中存储的每个更新表的项和tar中的进行比较,如果距离是1且目标网络在tar中,则将该路由的表存储在tables_n中。接着将tables_n中的项依次与本路由表的项进行比较(见下面的流程图) 。最后一步就是对故障的处理,如果有故障的路由,则将以故障路由为下一跳的所有项的距离设置为不可达(16)。
代码片段:
def update(table,tables,table_new):#更新自己的路由表
global str_update
table = copy.deepcopy(table)
tables = copy.deepcopy(tables)
#找出相邻的路由,并且得到更新表
tar = []
for i in table[1]:
if i[1]==1:
tar.append(i[0])
tables.remove(table)
tables_n = copy.deepcopy(tables)
for each in tables:
flag = False
for t in each[1]:
if t[0] in tar and t[1] == 1:
flag = True
break
else:
pass
if not flag:
tables_n.remove(each)
#开始更新
for each in tables_n:
str_update += '\n' + time_now() + '\n路由器%s收到了来自%s的更新表\n' % (table[0],each[0])
table_n = copy.deepcopy(table)
for each in tables_n:
n = each[0]
for tu in each[1]:
tu[1] += 1
if tu[1] == 17:
tu[1] = 16
tu[2] = n
f = False
for t in table_n[1]:
if t[0] == tu[0]:#如果目标网络相同
if t[2] == n:#如果下一跳相同
table_n[1][table_n[1].index(t)] = tu
str_update += '\n' + time_now() + '\n路由器%s从路由器%s更新了表项:\n(目标地址:%s,距离:%d,下一跳:%s)——>\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,t[0],t[1],t[2],tu[0],tu[1],tu[2])
else:#目标网络不同
if tu[1] < t[1]:
table_n[1][table_n[1].index(t)] = tu
str_update += '\n' + time_now() + '\n路由器%s从路由器%s更新了表项:\n(目标地址:%s,距离:%d,下一跳:%s)——>\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,t[0],t[1],t[2],tu[0],tu[1],tu[2])
f = True
break
if not f:
table_n[1].append(tu)
str_update += '\n' + time_now() + '\n路由器%s从路由器%s添加了新的表项:\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,tu[0],tu[1],tu[2])
#故障处理
for i in table_n[1]:
if i[2] == luyou_wrong:
i[1] = 16
lock.acquire()
table_new.append(table_n)
lock.release()
**
**
1. 输入一条信息,并点击添加
**
如果输入的距离不是1-16的数字(浮点数会舍去小数部分),会提示错误,比如输入字母或者大于16或小于1的数字
我们按照NET5-A-NET1-B-NET2-C-NET3-D-NET4-E-NET5的拓扑结构输入信息
2.点击更新,分别看一下路由表信息和日志信息:
路由表信息会记录每一次的历史路由表信息日志
日志信息则会记录下每一次添加表项 ,更新表项的结果
第一次更新之后,每个路由表变成了四项,我们再点击一次更新
现在每个路由器变成了5项,且表项也都正确。
再次点击更新
我们看到下一跳为A的路由表项的距离都变成了16(不可达)
我们保存到了桌面的’路由表.txt’中,找到并打开查看,发现路由信息已经保存进去了。
**
from tkinter import *
import threading,time,copy,os
#全局变量
lock = threading.Lock() #进程锁
tables = [] #网络上的路由表的集合
table_new = [] #更新后的路由表的集合
#通过这两个表的交换赋值来更新
luyou_wrong = '--' #故障的表的名字(默认一次只能故障一个路由)
#每次更新控件显示的字符串
str_send = '' #发送了自己路由表的信息
str_update = '' #更新了自己路由表的信息
def time_now():#用于获得当前时间
return str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
def main():#主函数
def add(data,tables):#由用户添加每个路由器的初始路由表
if tables:
flag = False
for i in range(len(tables)):
if data[0] == tables[i][0]:
if [data[1],data[2],data[3]] in tables[i][1]:
string = '路由器%s中已有该表项(目标地址:%s,距离:%d,下一跳:%s)' % (data[0],data[1],data[2],data[3])
else:
tables[i][1].append([data[1],data[2],data[3]])
string = '向路由器%s添加了表项(目标地址:%s,距离:%d,下一跳:%s)' % (data[0],data[1],data[2],data[3])
flag = True
break
if not flag:
tables.append([data[0],[[data[1],data[2],data[3]]]])
string = '添加了%s路由器\n向%s路由器添加了表项(目标地址:%s,距离:%d,下一跳:%s)' % (data[0],data[0],data[1],data[2],data[3])
else:
tables.append([data[0],[[data[1],data[2],data[3]]]])
string = '添加了%s路由器\n向%s路由器添加了表项(目标地址:%s,距离:%d,下一跳:%s)' % (data[0],data[0],data[1],data[2],data[3])
log(string)
return tables
def send(table):#向相邻网络发送自己的路由表
string = table[0] + '向相邻路由发送了自己的路由表 '
global str_send
str_send += time_now() + '\n' + string + '\n'
def update(table,tables,table_new):#更新自己的路由表
global str_update
table = copy.deepcopy(table)
tables = copy.deepcopy(tables)
#找出相邻的路由,并且得到更新表
tar = []
for i in table[1]:
if i[1]==1:
tar.append(i[0])
tables.remove(table)
tables_n = copy.deepcopy(tables)
for each in tables:
flag = False
for t in each[1]:
if t[0] in tar and t[1] == 1:
flag = True
break
else:
pass
if not flag:
tables_n.remove(each)
#开始更新
for each in tables_n:
str_update += '\n' + time_now() + '\n路由器%s收到了来自%s的更新表\n' % (table[0],each[0])
table_n = copy.deepcopy(table)
for each in tables_n:
n = each[0]
for tu in each[1]:
tu[1] += 1
if tu[1] == 17:
tu[1] = 16
tu[2] = n
f = False
for t in table_n[1]:
if t[0] == tu[0]:#如果目标网络相同
if t[2] == n:#如果下一跳相同
table_n[1][table_n[1].index(t)] = tu
str_update += '\n' + time_now() + '\n路由器%s从路由器%s更新了表项:\n(目标地址:%s,距离:%d,下一跳:%s)——>\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,t[0],t[1],t[2],tu[0],tu[1],tu[2])
else:#下一跳不同
if (tu[1] < t[1] and t[1] != 16) or tu[1] == 16:
table_n[1][table_n[1].index(t)] = tu
str_update += '\n' + time_now() + '\n路由器%s从路由器%s更新了表项:\n(目标地址:%s,距离:%d,下一跳:%s)——>\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,t[0],t[1],t[2],tu[0],tu[1],tu[2])
f = True
break
if not f:
table_n[1].append(tu)
str_update += '\n' + time_now() + '\n路由器%s从路由器%s添加了新的表项:\n(目标地址:%s,距离:%d,下一跳:%s)\n' % (table[0],n,tu[0],tu[1],tu[2])
#故障处理
for i in table_n[1]:
if i[0] == luyou_wrong and i[1] == 1:
i[1] = 16
lock.acquire()
table_new.append(table_n)
lock.release()
def threads(tables,table_new):#多线程发送和更新,做到每一个路由器同时发送和更新(设置接收信息的时间为1s)
global str_send
global str_update
str_send = ''
str_update = ''
threadpool_1 = []
for each in tables:
th = threading.Thread(target= send, args= (each,))
threadpool_1.append(th)
for th in threadpool_1:
th.start()
for th in threadpool_1:
threading.Thread.join(th)
t2.config(state = NORMAL)
t2.insert(INSERT,'--------------------------------------------------\n发送情况:\n')
t2.insert(INSERT,str_send)
t2.see(END)
t2.config(state = DISABLED)
time.sleep(1)
threadpool_2 = []
for each in tables:
th = threading.Thread(target= update, args= (each,tables,table_new))
threadpool_2.append(th)
for th in threadpool_2:
th.start()
for th in threadpool_2:
threading.Thread.join(th)
t2.config(state = NORMAL)
t2.insert(INSERT,'--------------------------------------------------\n更新情况:\n')
t2.insert(INSERT,str_update)
t2.see(END)
t2.config(state = DISABLED)
return table_new
def log(string):#更新日志的函数
string = time_now() + '\n' + string + '\n'
t2.config(state = NORMAL)
t2.insert(INSERT,string)
t2.see(END)
t2.config(state = DISABLED)
def show_add(tables):#用户添加初始路由表时更新路由信息
string = ''
string += '--------------------------------------------------\n'
string += '更新时间:' + str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) + '\n'
for each in tables:
string += '路由器' + each[0]+':\n'
for i in range(len(each[1])):
string += '('+str(i+1) + ')' + ' 目标网络:'+ each[1][i][0] + ' 距离:'+ str(each[1][i][1]) + ' 下一跳:' + each[1][i][2] + '\n'
t1.config(state = NORMAL)
t1.delete(1.0,END)
t1.insert(INSERT,string)
t1.see(END)
t1.config(state = DISABLED)
def show_up(tables):#每次更新路由表时更新路由信息
string = ''
string += '--------------------------------------------------\n'
string += '更新时间:' + str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) + '\n'
for each in tables:
string += '路由器' + each[0]+':\n'
for i in range(len(each[1])):
string += '('+str(i+1) + ')' + ' 目标网络:'+ each[1][i][0] + ' 距离:'+ str(each[1][i][1]) + ' 下一跳:' + each[1][i][2] + '\n'
t1.config(state = NORMAL)
t1.insert(INSERT,string)
t1.see(END)
t1.config(state = DISABLED)
#控件的callback函数
def add_callback(tables):#添加按钮
distance = e_distance.get()
if distance.isdigit():
name = e_name.get()
net_tar = e_net_tar.get()
next_ = e_next_.get()
if name != '' and net_tar != '' and distance != '':
distance = int(distance)
if distance>=1 and distance<=16:
data = [name,net_tar,distance,next_]
tables = add(data,tables)
show_add(tables)
else:
messagebox.showinfo('警告','距离应当为1-16的整数!')
else:
messagebox.showinfo('警告','路由器名称,目标网络和距离不能为空!')
else:
messagebox.showinfo('警告','距离应当为1-16的整数!')
def update_callback(tables,table_new):#更新按钮
table_new.clear()
tables_n = threads(tables,table_new)
tables.clear()
tables.extend(tables_n)
show_up(tables)
return tables
def wrong_callback(tables):#故障按钮
global luyou_wrong
name_w = e_name_w.get()
if name_w == '':
messagebox.showinfo('警告','故障网络名称不能为空!')
else:
luyou_wrong = name_w
messagebox.showinfo('通知','网络%s已设置为故障!' % name_w)
t2.config(state = NORMAL)
t2.insert(INSERT, '网络%s故障!' % name_w)
t2.see(END)
t2.config(state = DISABLED)
def save_info_callback():#保存路由信息
if not t1.get(1.0,END).isspace():
file = filedialog.asksaveasfilename(defaultextension = '.txt', filetypes = [('txt,TXT','.txt'),('ALL','.*')])
os.chdir(os.path.dirname(file))
f = open(os.path.basename(file),'w')
f.write(t1.get(1.0,END))
f.close()
messagebox.showinfo('通知','路由表信息已保存在%s中!' % file)
else:
messagebox.showinfo('警告','路由表信息为空!')
def save_log_callback():#保存日志信息
if not t2.get(1.0,END).isspace():
file = filedialog.asksaveasfilename(defaultextension = '.txt', filetypes = [('txt,TXT','.txt'),('ALL','.*')])
os.chdir(os.path.dirname(file))
f = open(os.path.basename(file),'w')
f.write(t2.get(1.0,END))
f.close()
messagebox.showinfo('通知','日志信息已保存在%s中!' % file)
else:
messagebox.showinfo('警告','路由表信息为空!')
#GUI界面
root = Tk()
root.title('基于距离向量算法的路由协议的实现')
#左边的输入框
m_l = PanedWindow(showhandle = True, sashrelief = SUNKEN)
m_l.pack(fill = BOTH, expand = 1, padx = 10, pady = 10)
frame_l = LabelFrame(m_l, text = '输入路由信息:', font = 18, padx = 5, pady = 5)
frame_l.pack(padx = 10, pady = 10)
Label(frame_l, text = '路由器名称:', font = 16).grid(row = 0, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '目的网络:', font = 16).grid(row = 1, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '距离:', font = 16).grid(row = 2, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '下一跳:', font = 16).grid(row = 3, column = 0, sticky = W, pady = 5)
e_name = Entry(frame_l, justify = CENTER)
e_name.grid(row = 0, column = 1)
e_net_tar = Entry(frame_l, justify = CENTER)
e_net_tar.grid(row = 1, column = 1)
e_distance = Entry(frame_l, justify = CENTER)
e_distance.grid(row = 2, column = 1)
e_next_ = Entry(frame_l, justify = CENTER)
e_next_.grid(row = 3, column = 1)
b_add = Button(frame_l, text = '添加', font = 18, command = lambda : add_callback(tables), padx = 20, pady = 5)
b_add.grid(row = 4, column = 0, pady = 5)
b_start = Button(frame_l, text = '更新', font = 18, command = lambda : update_callback(tables,table_new), padx = 20, pady = 5)
b_start.grid(row = 4, column = 1, pady = 5)
Label(frame_l, text = '', font = 16).grid(row = 5, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '模拟网络故障:', font = 18).grid(row = 6, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '故障网络名称:', font = 16).grid(row = 7, column = 0, sticky = W, pady = 5)
e_name_w = Entry(frame_l, justify = CENTER)
e_name_w.grid(row = 7, column = 1)
b_w = Button(frame_l, text = '故障', font = 18, command = lambda : wrong_callback(tables), padx = 20, pady = 5)
b_w.grid(row = 8, column = 0, pady = 5, columnspan = 2)
Label(frame_l, text = '', font = 16).grid(row = 9, column = 0, sticky = W, pady = 5)
Label(frame_l, text = '保存信息:', font = 18).grid(row = 10, column = 0, sticky = W, pady = 5)
b_save_info = Button(frame_l, text = '保存路由表', font = 16, command = save_info_callback, padx = 10, pady = 5)
b_save_info.grid(row = 11, column = 0, pady = 5)
b_save_log = Button(frame_l, text = '保存日志', font = 16, command = save_log_callback, padx = 10, pady = 5)
b_save_log.grid(row = 11, column = 1, pady = 5)
#右边的信息和log
m_r = PanedWindow(orient = VERTICAL, showhandle = True, sashrelief = SUNKEN)
frame1 = LabelFrame(m_r, text = '路由表信息:', font = 16, padx = 5, pady = 5)
frame1.pack(padx = 10, pady = 10)
sb1 = Scrollbar(frame1)
sb1.pack(side = RIGHT, fill = Y)
t1 = Text(frame1, width = 60, height = 20, font = 16, state = DISABLED, yscrollcommand = sb1.set)
t1.pack(side = LEFT, fill = BOTH)
sb1.config(command = t1.yview)
m_r.add(frame1)
frame2 = LabelFrame(m_r, text = '日志信息:', font = 16, padx = 5, pady = 5)
frame2.pack(padx = 10, pady = 10)
sb2 = Scrollbar(frame2)
sb2.pack(side = RIGHT, fill = Y)
t2 = Text(frame2, width = 60, height = 15, font = 16, state = DISABLED, yscrollcommand = sb2.set)
t2.pack(side = LEFT, fill = BOTH)
sb2.config(command = t2.yview)
m_r.add(frame2)
m_l.add(frame_l)
m_l.add(m_r)
mainloop()
if __name__=='__main__':
main()