Py 的魔力转圈圈:让我们一起用 Python3 & Tkinter 实现一个报文模拟器(附简单服务端代码)

py 魔力 转圈 圈圈 让我们


一、引言

在工作中,我们总会遇到这样的需求:

我们需要向服务端程序发送指定格式的报文,然后服务端进行一定的处理之后,再向我们发回处理后的报文。

或者说,我们总会需要:

在编写服务端运行的程序的时候,总想要有一个能够模拟接收报文、监控报文并且还能够回复报文的一个客户端程序,这样就可以方便我们调试编写我们的服务端运行的程序逻辑。

前者是编写客户端程序的视角,后者是编写服务端程序的视角。

不管怎么说,我们总会想要一个报文模拟器,能够接收、监控、发送报文,可以方便我们客户端与服务端报文交互相关的开发测试工作。

我也一直有这么一个想法,正好最近遇到了一个报文格式相对比较复杂的项目,不自己编写一个报文模拟器的话,服务端程序的逻辑调试将会非常麻烦。

因此,我对这个报文模拟器提出了自己的需求:

  1. 可以接收服务端发来的报文信息
  2. 可以向服务端发送指定的报文信息
  3. 可以滚动监控报文的接收与发送
  4. 可以编辑报文保存到本地,也可以从本地读取报文
  5. 为了让这个项目自成一体,最好加上一个自行编写的简单的服务端程序

于是,经过了几天的鏖战,我终于算是非常简陋的实现了上述的需求:
1

那么,现在,就让我们一起来实现这个报文模拟器吧:)

ps: 对这个项目的代码感兴趣的同学,可以来我的 GitHub
wangying2016/Packet_Simulator

二、需求分析:选择 Python3 & Tkinter

既然要做一个报文模拟器,也给自己提出了引言中提到的 5 个需求,那么我们就需要思考如果去实现它。

1. 技术选择

技术上,这里我选择了 Python3 & Tkinter,至于为什么。。。

因为我喜欢 Python3 & Tkiner 呀 :)

不过话说回来,对于我们程序员来说,一些工作中的辅助工具,当然是编写难度越简单越好,编写代码量越少越好,选择的语言当是库越强大越好,其语言原生支持的 UI 库也能基本达到我们的需求即可。

于是乎,我选择了 Python3,另外又因为 Tkinter 是 Python3 原生支持的跨平台的 UI 库,其强大而又简单易学,因此实现的工具就这么决定下来了。

2. 业务选择

有些人可能会觉得奇怪,我们编写一个小工具,为什么还涉及到业务的选择呢?

这是当然的,尽管我们只是想要编写一个报文模拟器,但是我们需要构建一个简单的场景去让它跑起来。这个场景中,包括我们服务端的程序,包括我们需要去定义的服务端与客户端交互的报文格式,这些都是需要定义好的。

另外,我们将自己的业务定义的越简单越具有代表性,那么后续我们将这个报文模拟器移植到具体的项目中使用的时候,也好进行扩展,比如说服务端与客户端交互的报文格式,涉及到报文的加解密方式、报文的读取与写入等等逻辑,这些都是需要根据具体的项目进行个性化扩展的。

这里,我们简单定义以下的业务规则:

  1. 场景范围
    报文的发送使用短连接的方式进行,也就是每次发送完报文即关闭 socket 连接。另外,不论是服务端还是客户端,都需要保持一个长时间监听的 socket 连接,主要负责对对方发送的报文的监听。
  2. 报文格式
    报文的格式因项目而异,这里我们作为实验项目,定义最简单的就好了,这里我定义如下:
    2

我们做好了技术和业务上的选择,剩下来就是进行代码的编写了,从哪部分开始呢?

当然是 UI。

三、界面设计:强大而又简单的 Tkinter

在我的代码文件 Sim_Sender.py 中,类 GUI 是用来实现界面设计的关键的类。

其中的 __init__ 方法用来初始化界面布局,布局包括一个输入框、一个文本框、三个按钮以及一个滚动文本框。

另外,控件所绑定的触发函数也定义在 GUI 类中,还包括本地报文文件的读和写,以及按钮的触发函数、滚动文本框的内容的写入等等。

这里,我挑几个重药的地方解释一下吧。

1. 窗口建立与初始化

Tkinter 中的窗口建立是通过 Tk() 函数进行的,其返回的值类似于窗口句柄,可以进行窗口大小和位置的设定。

继承于 Frame(框架类)的 GUI 类是窗口布局的控制类,其中初始化了我们界面上显示的这种种的控件。

if __name__ == '__main__':
...
# Gui
root = Tk()
app = GUI()
...
root.geometry("350x600+300+300")
root.mainloop()

代码中,在定义了窗口位置和大小之后,root.mainloop() 就是我们 Windows 编程中最熟悉的窗口消息循环啦:)

窗口的布局使用的是 pack 布局方式(Tkinter 支持 pack/grid/place 三种布局)。布局最核心的代码当然是 GUI 类中的 init_ui() 函数了:

 def init_ui(self):
self.master.title("报文模拟器")
self.pack(fill=BOTH, expand=True)
self.frame1 = Frame(self)
self.frame1.pack(fill=X, expand=True)
lbl1 = Label(self.frame1, text="识别代码", width=10)
lbl1.pack(side=LEFT, padx=5, pady=5)
self.entry = Entry(self.frame1)
self.entry.pack(fill=X, padx=5, expand=True)
self.frame2 = Frame(self)
self.frame2.pack(fill=X, expand=True)
lbl2 = Label(self.frame2, text="报文内容", width=10)
lbl2.pack(side=LEFT, anchor=N, padx=5, pady=5)
self.txt = Text(self.frame2, height=10)
self.txt.pack(fill=X, pady=5, padx=5, expand=True)
self.frame3 = Frame(self)
self.frame3.pack(fill=X, expand=True)
button1 = Button(self.frame3, text="打开本地", command=self.open_local)
button1.pack(side=LEFT, padx=5, pady=5, fill=X, expand=True)
button2 = Button(self.frame3, text="保存本地", command=self.save_local)
button2.pack(side=LEFT, padx=5, pady=5, fill=X, expand=True)
button3 = Button(self.frame3, text="发送报文", command=self.send_msg)
button3.pack(side=LEFT, padx=5, pady=5, fill=X, expand=True)
self.frame4 = Frame(self)
self.frame4.pack(fill=X, expand=True)
lbl3 = Label(self.frame4, text='日志监控')
lbl3.pack(side=LEFT, padx=5, pady=5)
self.frame5 = Frame(self)
self.frame5.pack(fill=BOTH, expand=True)
self.log = scrolledtext.ScrolledText(self.frame5, height=55, width=150)
self.log.pack(side=LEFT, pady=5, padx=5, expand=True)
self.log.focus_set()
self.add_log('packet simulator', 'start listen')

Tkinter 的布局并不难学,只需要能够自行去寻找源代码中的信息,能够上网搜索相关教程即可,这里就不再赘述了。

2. 滚动文本框

在界面中,最难的控件莫过于最下面的日志监控的滚动文本框了。
3
这个是使用的 Tkinter 的 scrolledtext 控件,其中最重要的加入日志信息的函数实现如下:

 def add_log(self, title, msg):
tm = time.localtime(time.time())
fmt_msg = '%s-%s-%s %s:%s:%s %s\n%s\n\n' % (tm.tm_year,
'{:0>2}'.format(tm.tm_mon),
tm.tm_mday,
'{:0>2}'.format(tm.tm_hour),
'{:0>2}'.format(tm.tm_min),
'{:0>2}'.format(tm.tm_sec),
title,
msg)
self.log.insert(INSERT, fmt_msg)
self.log.focus_set()
self.log.yview_moveto(1)

其中,前面都是匹配时间格式,后面的 self.log.yview_moveto(1) 函数可以保持当前显示永远在最后一行(实现滚动跟踪的功能)。

3. 还有其他疑惑…

如果还有其他疑惑,我推荐网上的一份 Tkinter 教程,非常利于新手的学习:
Python Programming with Tkinter

现在,我们实现了界面的布局,也能够模拟滚动的日志信息显示了,那么接下来我们做什么呢?

Socket 编程:)

四、Socket 编程:服务端与客户端的交互

归根结底,这到底还是一个服务端与客户端交互的项目。因此除了报文模拟器,我们还要编写一个服务端的程序。

当然了,为了尽量简化这个项目的结构,我们使用了发起者短连接的方式。也就是,当报文发起者发送报文的时候,自行创建一个 socket 连接,发送完毕后立即关闭。对于报文监听者来说,就需要一直挂着监听。

1. 服务端程序

服务单程序还算比较简单,只需要一直挂着监听报文即可,只是在接收到报文之后,需要另起一个 socket 连接进行报文的回复。

下列是监听报文的代码:

class Server:
"""
Listen for client.
"""
def __init__(self):
# Create socket
tcp_server_socekt = socket(AF_INET, SOCK_STREAM)
# Bind ip & port
server_address = (server_ip, server_port)
tcp_server_socekt.bind(server_address)
# Begin listen
tcp_server_socekt.listen(1)
print('start listen\n')
# Server long connection, client short connection
while True:
# Listen for client's connection
new_socket, client_address = tcp_server_socekt.accept()
# Receive client data
recv_data = str(new_socket.recv(1024), encoding='utf-8')
# if data length is 0, because client close the connect
if len(recv_data) > 0:
print('recv data = [%s]\n' % recv_data)
else:
print('recv data = 0\n')
# Processing message
processor = Processor(recv_data)
send_data = processor.get_msg()
client = Client(send_data)
print('send data = [%s]\n' % send_data)
# Close the client socket
new_socket.close()
# Stop listen
tcp_server_socekt.close()

可以看到,我们在挂起监听之后,每次接收到报文信息之后,都是调用了 Client() 类进行报文的回复,在这个类中,我们新起了一个连接自发送者的 socket 连接:

class Client:
"""
Send message to client.
"""
def __init__(self, data):
# Create socket
self.tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# Bind ip & port
self.server_address = (client_ip, client_port)
# Connect to client
self.tcp_client_socket.connect(self.server_address)
# Send data
self.tcp_client_socket.send(bytes(data.encode("utf-8")))
# Close connect
self.tcp_client_socket.close()

所以,服务端程序的逻辑是非常简单的,就是一直监听报文模拟器的报文,然后接收到报文之后进行处理,最后新起一个连接进行返回。

2. 客户端程序

实际上客户端程序同服务端程序很类似,也是挂着监听服务端的报文,然后新起一个 socket 连接发送报文。

但是,客户端程序又有不一样的地方,那就是需要考虑到多线程去处理 UI 和监听线程。

这里,在 GUI 类的初始化中,我新起了一个线程进行报文的监听:

class GUI(Frame):
"""
GUI with tkinter.
"""
def __init__(self):
super().__init__()
# Instance variable
...
# Init ui
self.init_ui()
# Begin listen
t = threading.Thread(target=network)
t.setDaemon(True)
t.start()
def network():
server = Server()
class Server:
"""
Listen message from server.
"""
def __init__(self):
...

上述代码应该能够让你看清楚这个项目的结构,是非常清晰的。

对于客户端报文发送来说,实际上也是很雷同服务端的:

 def send_msg(self):
# Get parameter
argv1 = self.entry.get().strip()
argv2 = self.txt.get('1.0', END).strip().replace('\n', '')
if len(argv1) != 4:
messagebox.showerror('错误', '请输入 4 位识别代码')
return
print('argv1 = [%s], argv2 = [%s]' % (argv1, argv2))
# Make message
maker = Maker()
msg = maker.get_msg(argv1, argv2)
print('send data: \n[%s]\n' % msg)
# Send Data
client = Client(msg)
class Client:
"""
Send message to server.
"""
def __init__(self, data):
# Create socket
self.tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# Bind ip & port
self.server_address = (server_ip, server_port)
# Connect to server
self.tcp_client_socket.connect(self.server_address)
# Send data
self.tcp_client_socket.send(bytes(data.encode("utf-8")))
# Close connect
self.tcp_client_socket.close()
# Add log
data = 'data = [%s]' % data
app.add_log('send data', data)

这里进行报文的组包,然后调用 Client 类新建一个 socket 连接进行报文的发送。

至此,这个项目最核心的网络编程相关的内容就编写完了。

那么,还剩下什么呢?

报文的读写:)

五、报文读写:生成和解读

我们之前在需求分析的时候就对报文格式进行了定义,那么对应的,我们需要一个按照报文格式生成报文的类,和一个按照报文格式解读报文的类。

生成报文的类:

class Maker:
"""
Packet generate class.
"""
def __init__(self):
self.index = ''
self.content = ''
self.msg = ''
def get_msg(self, index, content):
self.index = index
self.content = content
length = 6 + 4 + 2 + len(content)
self.msg = '{:0>6}'.format(length) + '|' + index + '|' + content
return self.msg
def get_index(self):
return self.index
def get_content(self):
return self.content

解读报文的类:

class Reader:
"""
Packet analysis class.
"""
def __init__(self, msg):
self.msg = msg
self.index = msg[7:11]
self.content = msg[12:]
def get_index(self):
return self.index
def get_content(self):
return self.content

这里因为我的报文格式是我自己定义的,就不再赘述了,值得一说的是,根据项目的复杂程度不同,只需要扩展这两个类,即可对不同项目进行支持了:)

至此,这个项目也就做完了 ^_^
完结撒花 :)

六、总结

作为一个消费者就是我自己的项目来说,只要我用着舒服也就可以了。不过这个报文模拟器当然还有着这样或那样的不足:

  1. 界面不够友好
  2. 功能还不够强大

等等,这些都是可以在后续的项目开发过程中,根据自己的想法进行添加和实现。

不过话说回来,我真的很喜欢 Python 的 Tkinter 界面库,虽然没有那么好看,但是只要能够跑 Python3 的地方,就能跑 Tkinter 不得不说真的是非常的方便,想想 IDLE 就是这玩意儿写的,除了界面不够那么炫酷,其他基本也就满足需求了,特别适合这种随手就来的小工具项目啊!

Python3 & Tkinter 的学习之路还有很长
To be Stronger:)

版权声明
本文为[曾经去过跨越一个小时的地方]所创,转载请带上原文链接,感谢
https://wangying.blog.csdn.net/article/details/90409877

  1. 利用Python爬虫获取招聘网站职位信息
  2. Using Python crawler to obtain job information of recruitment website
  3. Several highly rated Python libraries arrow, jsonpath, psutil and tenacity are recommended
  4. Python装饰器
  5. Python实现LDAP认证
  6. Python decorator
  7. Implementing LDAP authentication with Python
  8. Vscode configures Python development environment!
  9. In Python, how dare you say you can't log module? ️
  10. 我收藏的有关Python的电子书和资料
  11. python 中 lambda的一些tips
  12. python中字典的一些tips
  13. python 用生成器生成斐波那契数列
  14. python脚本转pyc踩了个坑。。。
  15. My collection of e-books and materials about Python
  16. Some tips of lambda in Python
  17. Some tips of dictionary in Python
  18. Using Python generator to generate Fibonacci sequence
  19. The conversion of Python script to PyC stepped on a pit...
  20. Python游戏开发,pygame模块,Python实现扫雷小游戏
  21. Python game development, pyGame module, python implementation of minesweeping games
  22. Python实用工具,email模块,Python实现邮件远程控制自己电脑
  23. Python utility, email module, python realizes mail remote control of its own computer
  24. 毫无头绪的自学Python,你可能连门槛都摸不到!【最佳学习路线】
  25. Python读取二进制文件代码方法解析
  26. Python字典的实现原理
  27. Without a clue, you may not even touch the threshold【 Best learning route]
  28. Parsing method of Python reading binary file code
  29. Implementation principle of Python dictionary
  30. You must know the function of pandas to parse JSON data - JSON_ normalize()
  31. Python实用案例,私人定制,Python自动化生成爱豆专属2021日历
  32. Python practical case, private customization, python automatic generation of Adu exclusive 2021 calendar
  33. 《Python实例》震惊了,用Python这么简单实现了聊天系统的脏话,广告检测
  34. "Python instance" was shocked and realized the dirty words and advertisement detection of the chat system in Python
  35. Convolutional neural network processing sequence for Python deep learning
  36. Python data structure and algorithm (1) -- enum type enum
  37. 超全大厂算法岗百问百答(推荐系统/机器学习/深度学习/C++/Spark/python)
  38. 【Python进阶】你真的明白NumPy中的ndarray吗?
  39. All questions and answers for algorithm posts of super large factories (recommended system / machine learning / deep learning / C + + / spark / Python)
  40. [advanced Python] do you really understand ndarray in numpy?
  41. 【Python进阶】Python进阶专栏栏主自述:不忘初心,砥砺前行
  42. [advanced Python] Python advanced column main readme: never forget the original intention and forge ahead
  43. python垃圾回收和缓存管理
  44. java调用Python程序
  45. java调用Python程序
  46. Python常用函数有哪些?Python基础入门课程
  47. Python garbage collection and cache management
  48. Java calling Python program
  49. Java calling Python program
  50. What functions are commonly used in Python? Introduction to Python Basics
  51. Python basic knowledge
  52. Anaconda5.2 安装 Python 库(MySQLdb)的方法
  53. Python实现对脑电数据情绪分析
  54. Anaconda 5.2 method of installing Python Library (mysqldb)
  55. Python implements emotion analysis of EEG data
  56. Master some advanced usage of Python in 30 seconds, which makes others envy it
  57. python爬取百度图片并对图片做一系列处理
  58. Python crawls Baidu pictures and does a series of processing on them
  59. python链接mysql数据库
  60. Python link MySQL database