import ipaddress import sys import threading from tkinter import filedialog from SimUtil import * from SimObjs import SimPacket, SimHost, AllSimConnect, SimRouter, SimSwitch, SimHub, SimBase from dbUtil import search, execute_sql, delete_obj, truncate_db from PIL import Image as imim from packet import * class Message(Canvas): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.message = [] self.master = master self.scrollbar_y = tk.Scrollbar(master, orient="vertical", command=self.yview) self.scrollbar_y.pack(side="right", fill=BOTH) self.scrollbar_x = tk.Scrollbar(master, orient=HORIZONTAL, command=self.xview) self.scrollbar_x.pack(side="bottom", fill=BOTH) self.config(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set) def show_message(self, message, color="#5fa8fe"): for mes in message.split("\n"): self.message.append({"message": mes, "color": color}) num = 0 self.delete("message") for function in self.message: self.create_text(20, 25 * num + 10, anchor="nw", text=function["message"], font=("", 15), fill=function["color"], tags="message") num += 1 self.update() self.configure(scrollregion=self.bbox("all")) self.scrollbar_y.set(1.0, 1.0) self.yview_moveto(1.0) class NetWorkAnalog(Canvas): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.master = master self.router_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/路由器@2x.png").resize((40, 40))) self.switch_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/交换机@2x.png").resize((40, 40))) self.hub_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/集线器@2x.png").resize((40, 40))) self.host_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/主机@2x.png").resize((40, 40))) self.width = int(self.cget("width")) self.height = int(self.cget("height")) self.canvas_size = [150, 0, int(self.width * 0.8), int(self.height * 0.8)] self.label_top_img = ImageTk.PhotoImage(imim.open("../datas/images/右上框@2x.png").resize((int(self.width - self.width * 0.85), int(self.canvas_size[3] * 0.4)))) self.label_bottom_img = ImageTk.PhotoImage(imim.open("../datas/images/右下框@2x.png").resize((int(self.width - self.width * 0.85), int(self.canvas_size[3] * 0.45)))) self.message_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/日志消息展示框@2x.png").resize((self.width - 60, self.height - self.canvas_size[3] - 30)) ) self.text_back_img = ImageTk.PhotoImage( Image.open("../images/文字背景@3x.png").resize((155, 90)) ) message_frame = Frame(self, width=self.width - 60, height=self.height - self.canvas_size[3] - 30) message_frame.place(x=15, y=self.canvas_size[3] + 15, anchor=NW) self.message = Message(message_frame, width=self.width - 60, height=self.height - self.canvas_size[3] - 30) self.message.pack() self.message.configure(bg="#edf8ff") self.configure(bg="#ddeafe") self.filename = ImageTk.PhotoImage(imim.open("../datas/images/背景@2x.png").resize((self.width, self.canvas_size[3]))) self.create_image(0, 0, image=self.filename, anchor=NW) self.chose = self.host_img self.AllSimObjs = {} self.conns = [] self.drawLine = True # 画线标志 self.line_start_obj = None self.line_end_obj = None self.line_start_ifs = None self.line_end_ifs = None self.chose_obj = None self.show_label_flag = True self.show_interface_flag = True self.trans_obj = set() self.create_widget() def is_chose(self): """ 当被选中时,绘制选中框 :return: """ self.delete("rectangle") self.create_rectangle(self.chose_obj.ObjX - 35, self.chose_obj.ObjY - 35, self.chose_obj.ObjX + 15, self.chose_obj.ObjY + 15, outline="red", tags="rectangle") def reload_data(self): # todo: 加载上一次程序运行的数据 """ 加载上一次程序运行时的数据 :return: """ self.AllSimObjs = {} self.conns = [] sim_obj_sql = "select * from sim_objs" sim_obj_data = search(sim_obj_sql) for index, row in sim_obj_data.iterrows(): # 初始化组件对象 sim_type = row["ObjType"] ObjX = row["ObjX"] ObjY = row["ObjY"] ConfigCorrect = row["ConfigCorrect"] ObjLable = row["ObjLabel"] ObjID = row["ObjID"] if sim_type == 1: tag = SimHost(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) elif sim_type == 2: tag = SimRouter(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) elif sim_type == 3: tag = SimSwitch(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) else: tag = SimHub(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) tag.create_img() self.AllSimObjs[tag.ObjID] = tag sim_conn_sql = "select s.conn_id, ConfigCorrect, node_id, node_ifs from sim_conn s join conn_config c on s.conn_id=c.conn_id" sim_conn_data = search(sim_conn_sql) conn_datas = {} for index, conn in sim_conn_data.iterrows(): if (conn["conn_id"], conn["ConfigCorrect"]) not in conn_datas: conn_datas[(conn["conn_id"], conn["ConfigCorrect"])] = [(conn["node_id"], conn["node_ifs"])] else: conn_datas[(conn["conn_id"], conn["ConfigCorrect"])].append((conn["node_id"], conn["node_ifs"])) for key, value in conn_datas.items(): conn_obj = AllSimConnect(self, self.AllSimObjs[value[0][0]], value[0][1], self.AllSimObjs[value[1][0]], value[1][1], key[1]) conn_obj.draw_line() self.AllSimObjs[value[0][0]].connections[value[0][1] - 1] = conn_obj # 将连接对象传入组件对象 self.AllSimObjs[value[1][0]].connections[value[1][1] - 1] = conn_obj self.conns.append(conn_obj) conn_obj.ConfigCorrect = key[1] def show_obj(self, AllSimObj, conns): self.AllSimObjs = AllSimObj self.conns = conns for key, sim_obj in self.AllSimObjs.items(): sim_obj.create_img() for conn in self.conns: conn.draw_line() def send_packet(self): """ 发送数据包 :return: """ hosts = {} for key, tag in self.AllSimObjs.items(): if tag.ObjType == 1 and tag.ConfigCorrect == 1: hosts[tag.ObjLabel] = tag.ObjID child2 = tk.Toplevel() child2.title("数据包配置") child2.geometry('392x306+200+110') child2.grab_set() # 设置组件焦点抓取。使焦点在释放之前永远保持在这个组件上,只能在这个组件上操作 tk.Label(child2, text='发送主机:', font=('黑体', 16)).grid(row=0, column=0, columnspan=2, sticky='w', pady=10) send_host = StringVar() host_combobox = ttk.Combobox( master=child2, # 父容器 height=5, # 高度,下拉显示的条目数量 width=18, # 宽度 state='readonly', # readonly(只可选) font=('', 16), # 字体 textvariable=send_host, # 通过StringVar设置可改变的值 values=list(hosts.keys()), # 设置下拉框的选项 ) host_combobox.grid(row=0, column=1, pady=10) tk.Label(child2, text='接收主机:', font=('黑体', 16)).grid(row=1, column=0, sticky='w', pady=10) # ,sticky='w'靠左显示 receive_host = StringVar() ttk.Combobox( master=child2, # 父容器 height=5, # 高度,下拉显示的条目数量 width=18, # 宽度 state='readonly', # readonly(只可选) font=('', 16), # 字体 textvariable=receive_host, # 通过StringVar设置可改变的值 values=list(hosts.keys()), # 设置下拉框的选项 ).grid(row=1, column=1, pady=10) tk.Label(child2, text='数据大小(kb):', font=('黑体', 16)).grid(row=2, column=0, sticky='w', pady=10) # ,sticky='w'靠左显示 packet_size = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) packet_size.grid(row=2, column=1, pady=10) tk.Label(child2, text='数据标签:', font=('黑体', 16)).grid(row=3, column=0, sticky='w', pady=10) # ,sticky='w'靠左显示 packet_label = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) packet_label.grid(row=3, column=1, pady=10) def send(): """ 发送数据包 :return: """ if send_host.get() == "" or receive_host.get() == "" or packet_size.get() == "" or packet_label.get() == "": messagebox.showerror("注意", message="信息不能为空") return if send_host.get() == receive_host.get(): messagebox.showerror("注意", message="发送主机和接收主机不能相同!") send_message = """发送主机:{}\n\nIP地址:{}\n\nMac地址:{}\n\n发送数据包大小:{}\n\n已发送数据数量:{}\n\n需要发送的数据包总数:{}""" receive_message = """接收主机:{}\n\nIP地址:{}\n\nMac地址:{}\n\n发送数据包大小:{}\n\n已接收数据数量:{}\n\n需要发送的数据包总数:{}""" send_host_obj = self.AllSimObjs[hosts[send_host.get()]] receive_host_obj = self.AllSimObjs[hosts[receive_host.get()]] pack_label = packet_label.get() pack_size = packet_size.get() child2.destroy() count = split_appdata(int(pack_size)) self.show_top_message(send_host_obj, message=send_message.format(send_host_obj.ObjLabel, send_host_obj.interface[0]["ip"], send_host_obj.interface[0]["mac"], pack_size, 0, count)) self.show_bottom_message(receive_host_obj, message=receive_message.format(receive_host_obj.ObjLabel, receive_host_obj.interface[0][ "ip"], receive_host_obj.interface[0][ "mac"], pack_size, 0, count)) for obj in self.trans_obj: self.delete(obj.ObjID + "detail_button") self.delete(obj.ObjID + "detail") self.unbind(obj.ObjID, "") send_host_obj.create_packet(receive_host_obj, pack_label, pack_size) tk.Button(child2, text='开启模拟', font=('黑体', 16), height=1, command=send).grid(row=5, column=0, sticky='e', pady=10) tk.Button(child2, text='取消', font=('黑体', 16), height=1, command=child2.destroy).grid(row=5, column=1, sticky='e', pady=10) # 建立画出对象详情的函数 def draw_detail(self, obj): # 判断type对象来画出详情表 if obj.ObjType == 1: # 当type对象为1 画出主机详情图 app_color = ("#94D050", '#00B0F0') trans_color = ("#94D050", '#00B0F0') ip_color = ("#94D050", '#00B0F0') mac_color = ("#94D050", '#00B0F0') elif obj.ObjType == 2: app_color = ("#D3D3D3", "#D3D3D3") trans_color = ("#D3D3D3", "#D3D3D3") ip_color = ("#94D050", '#00B0F0') mac_color = ("#94D050", '#00B0F0') elif obj.ObjType == 3: app_color = ("#D3D3D3", "#D3D3D3") trans_color = ("#D3D3D3", "#D3D3D3") ip_color = ("#D3D3D3", "#D3D3D3") mac_color = ("#94D050", '#00B0F0') else: app_color = ("#D3D3D3", "#D3D3D3") trans_color = ("#D3D3D3", "#D3D3D3") ip_color = ("#D3D3D3", "#D3D3D3") mac_color = ("#94D050", '#00B0F0') frist_x = obj.ObjX # 获取frist_x frist_y = obj.ObjY # 获取frist_y # 这里缺少一个删除其他详情的步骤 # 画出连接详情表的线 tag_id = obj.ObjID + "detail" if len(self.gettags(tag_id)) > 0: self.delete(tag_id) else: self.create_line((frist_x + 40, frist_y - 20), (frist_x + 80, frist_y - 20), (frist_x + 90, frist_y - 60), fill='#50abf8', tags=tag_id, width=2) # 画出详情表 self.create_image(frist_x + 50, frist_y - 140, image=self.text_back_img, anchor=NW, tags=tag_id) # 画出相应的绿条数据 self.create_text(frist_x + 70, frist_y - 130, text='应用层', tags=tag_id) self.create_text(frist_x + 70, frist_y - 107, text='传输层', tags=tag_id) self.create_text(frist_x + 70, frist_y - 84, text='IP 层', tags=tag_id) self.create_text(frist_x + 70, frist_y - 62, text='链路层', tags=tag_id) # 画出 右侧绿色和蓝色的类进度条 # 应用层 self.create_rectangle(frist_x + 197, frist_y - 135, frist_x + 170, frist_y - 120, fill=app_color[0], outline=app_color[0], tags=tag_id) self.create_rectangle(frist_x + 168, frist_y - 135, frist_x + 148, frist_y - 120, fill=app_color[1], outline=app_color[1], tags=tag_id) # 传输层 self.create_rectangle(frist_x + 197, frist_y - 115, frist_x + 160, frist_y - 100, fill=trans_color[0], outline=trans_color[0], tags=tag_id) self.create_rectangle(frist_x + 158, frist_y - 115, frist_x + 133, frist_y - 100, fill=trans_color[1], outline=trans_color[1], tags=tag_id) # IP 层 self.create_rectangle(frist_x + 197, frist_y - 93, frist_x + 150, frist_y - 78, fill=ip_color[0], outline=ip_color[0], tags=tag_id) self.create_rectangle(frist_x + 148, frist_y - 93, frist_x + 118, frist_y - 78, fill=ip_color[1], outline=ip_color[1], tags=tag_id) # 链路层 self.create_rectangle(frist_x + 197, frist_y - 70, frist_x + 133, frist_y - 55, fill=mac_color[0], outline=mac_color[0], tags=tag_id) self.create_rectangle(frist_x + 131, frist_y - 70, frist_x + 98, frist_y - 55, fill=mac_color[1], outline=mac_color[1], tags=tag_id) def trans_over(self, objs): print("传输完成") self.trans_obj = objs for obj in objs: obj.create_detail_button() self.tag_bind(obj.ObjID, "", lambda event, obj=obj: self.draw_detail(obj)) # 绑定右击事件 obj.is_packet = False obj.is_un_packet = False def clear_canvas(self): """ 清除画布 :return: """ ask = messagebox.askquestion(title='确认操作', message='确认要清除画布吗?') if ask == "no": return truncate_db() # 清除数据库 for tag_id, tag in self.AllSimObjs.items(): self.delete(tag_id) self.delete(tag_id + "text") for conn in self.conns: conn.delete_line() self.delete("rectangle") self.AllSimObjs.clear() self.conns.clear() def export_data(self): try: export = ExportUtil(sys.path[0] + "/database.xlsx") export.export() self.message.show_message("文件导出成功!文件位置在{}".format(sys.path[0] + "/database.xlsx")) except Exception as E: self.message.show_message(str(E), color="red") def import_data(self): truncate_db() for tag_id, tag in self.AllSimObjs.items(): self.delete(tag_id) # 删除画布中的所有组件 self.delete(tag_id + "text") for conn in self.conns: conn.delete_line() # 删除画布中的所有连接线 self.delete("rectangle") self.AllSimObjs.clear() # 删除当前所有组件对象 self.conns.clear() # 删除所有连接配置 file_path = filedialog.askopenfilename() # 获取用户选择的文件 if file_path: export = ExportUtil(file_path) export.import_data() self.message.show_message("文件导入成功!") self.reload_data() def show_top_message(self, obj, message): # todo: 显示网络配置信息 """ 显示网络配置信息 :return: """ self.delete("netSet") self.create_text(self.width * 0.82 + 120, 80, text="(" + obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 14, 'bold'), fill="#7030a0", tags="netSet") self.create_text(self.width * 0.82 + 20, 80 + 25, text=message, anchor="nw", font=('宋体', 14), tags="netSet") def show_bottom_message(self, obj, message): # todo: 显示路由表交换表信息 """ 显示路由交换表信息 :return: """ self.delete("routerSet") self.create_text(self.width * 0.82 + 120, self.canvas_size[3] / 2 + 50, text="(" + obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 14, 'bold'), fill="#7030a0", tags="routerSet") self.create_text(self.width * 0.82 + 20, self.canvas_size[3] / 2 + 50 + 25, text=message, anchor="nw", font=('宋体', 14), tags="routerSet") def create_config_button(self): # 创建一个菜单栏,这里我们可以把他理解成一个容器,在窗口的上方 menubar = tk.Menu(root) # 定义一个空菜单单元 setMenu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label='发送数据包', menu=setMenu) setMenu.add_command(label='发送数据包', command=self.send_packet) menubar.add_command(label='清除屏幕', command=self.clear_canvas) menubar.add_command(label='导出数据', command=self.export_data) menubar.add_command(label='导入数据', command=self.import_data) root.config(menu=menubar) def create_widget(self): # todo: 创建页面 """ 创建整体页面布局 :return: """ self.create_rectangle(self.canvas_size[0], self.canvas_size[1], self.canvas_size[2], self.canvas_size[3], outline="#7f6000", width=0) # 矩形框,左上角坐标,右下角坐标 self.create_image(self.width * 0.82, 30, image=self.label_top_img, anchor=NW) # 矩形框,左上角坐标,右下角坐标 self.create_text(self.width * 0.82 + 120, 30 + 15, text="发送主机详情", anchor="n", font=('微软雅黑', 18, 'bold')) self.create_image(self.width * 0.82, self.canvas_size[3] / 2, image=self.label_bottom_img, anchor=NW) # 矩形框,左上角坐标,右下角坐标 # round_rectangle(self, self.width * 0.82, self.canvas_size[3] / 2, self.width * 0.98, self.canvas_size[3] - 30, outline="#ffff00", width=2, fill="#f4b88e") # 矩形框,左上角坐标,右下角坐标 self.create_text(self.width * 0.82 + 120, self.canvas_size[3] / 2 + 15, text="接收主机详情", anchor="n", font=('微软雅黑', 18, 'bold')) # 显示左边的固定图片 img_height, text_height, split_width = 50, 40, 120 self.create_text(text_height, split_width + 40, text="路由器", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 self.create_image(img_height, split_width, image=self.router_img, anchor="nw") self.create_text(text_height, split_width * 2 + 40, text="交换机", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 self.create_image(img_height, split_width * 2, image=self.switch_img, anchor="nw") self.create_text(text_height, split_width * 3 + 40, text="集线器", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 self.create_image(img_height, split_width * 3, image=self.hub_img, anchor="nw") self.create_text(text_height, split_width * 4 + 40, text="主机", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 self.create_image(img_height, split_width * 4, image=self.host_img, anchor="nw") if __name__ == '__main__': root = Window() root.title('网络拓扑图') screen_width = root.winfo_screenwidth() # winfo方法来获取当前电脑屏幕大小 screen_height = root.winfo_screenheight() root_attr = { "width": screen_width * 0.83, "height": screen_height * 0.85, } size = '%dx%d+%d+%d' % (root_attr['width'], root_attr['height'], (screen_width - root_attr['width']) / 2, (screen_height - root_attr['height']) / 2 - 30) canvas = NetWorkAnalog(root, width=root_attr['width'], heigh=root_attr['height'], bg="white") canvas.place(x=0, y=0, anchor='nw') canvas.create_config_button() canvas.reload_data() root.geometry(size) root.mainloop()