Github源码

多线程/进程问题:以iperf3为例

在给一个项目写可视化的时候,项目需要用iperf3来进行网络测试。

ipef3分为服务器server端和客户client端两个进程,一般使用cmd打开,开启测试的界面是这个样子:

server端

image-20201128233626786

client端:

image-20201128233706734

第一个坑

为了能够使用python代码控制控制台打开iperf3.exe,使用==subprocess==模块来启动。

但是subprocess直接启动程序,需要将iperf3.exe的路径加入到系统变量里,这样就不需要cmd进入对应文件目录再启动:

第二个坑

在解决直接用命令打开ipef3.exe之后,就是用subprocess使用iperf3.exe -s和iperf3.exe -c 127.0.0.1这两个命令,应该这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def server():
with subprocess.Popen(["iperf3.exe", "-s"], stdout=subprocess.PIPE, universal_newlines=True) as process:
while True:
print("doing")
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
# with open('D:\ForStudy\python练习\练习\项目\out.txt', 'w+') as file:
# sys.stdout = file
# print(output.strip())
# sys.stdout = stdout#结果重定向到文档
a=output.strip()
rc = process.poll()

注意subprocess.Popen(["iperf3.exe", "-s"],只有这样写,才是正确的输入参数的格式,将-s这个参数和前面分开!!!!!

第三个坑

虽然我现在能够启动server和client并且实现了iperf推流,但是在这两个线程退出之后,我的vscode调试界面居然没有退出!!

QQ图片20201128234553

后来仔细想想,我是调用了个线程开启了iperf这个进程,因此虽然这次推流结束了但是iperf server服务进程还在,因此我还需要将这个进程退出才行:

1
2
3
4
5
6
7
8
9
if flag ==1:
print("client has shut")
with subprocess.Popen("taskkill /IM iperf3.exe /F", stdout=subprocess.PIPE, shell=True,universal_newlines=True) as process:
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())

这样,我就输入cmd命令将进程关闭了hhhh。简单粗暴又好用:
QQ图片20201128234853

感谢:雷学长

UDP报文解析

字符串与数组

bytearray操作用法

4字节数转化为浮点数

剩余任务:

  • 测试1、2的结果可视化
  • 测试3的三节点结果可视化

汉字pygame可视化

链接

12.25

要干的活:

1.SV,只显示两个节点:white_check_mark:

2.三段组网,数据根据V段信息,(x,y,z)(x_speed,y_speed,z_speed)来排列:white_check_mark:

3.三段组网,拓扑连线根据S段信息:white_check_mark:

4.根据S选中了谁,就点亮谁

v段node1的yz坐标出现nan,应该不是我的问题

tkinter的使用

tkinter实现勾选和反选

下拉框

Tkinter组件

单选框

测试平台代码解析

文件结构

  • product.py 主界面

  • items.py Node类(三节点组网时每个节点的信息) API_need类(所需的函数) Button 类(按钮的属性)

  • Remote.py

    • UDP_test.py

    • UDPPingerClient.py

    • 2-2-UDPPingServer.py

V段组网测试

QQ图片20210114230810

product.py ==77行==

if条件内为判断点击到了“V段测试”

1
2
3
4
5
6
7
if(点击到了“V段测试”)
print("click 1")
BK_flag=2#第二个页面
S_flag=0
Ceshi_1.if_click = 1#按钮变灰
Ceshi_2.if_click = 0
Ceshi_3.if_click=0

点击选择到V段之后,会点击测试客户端/测试服务器,下面以点击到服务器为例==102行==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(测试服务器)
print("click 4")
node2.if_click = 1
node3.if_click = 0

Ceshi_4.if_click = 1
Ceshi_5.if_click = 0
Ceshi_6.if_click = 0
V_waiting_flag=1#显示等待回复
if S_flag==0:#判断这是V段而不是S段
#监听回复报文
V_listen=Thread(target=api.V_config_server_receive, args=(api,))
V_listen.start()
print('V is listening')
#发送配置报文
time.sleep(1)
V_send=Thread(target=api.V_config_server_send, args=(api,))
V_send.start()

这段代码做了两件事,V_listen线程监听了25600端口,准备接受板子配置好之后返还的UDP报文;随后等待2s,V_send向25600端口发送配置报文:

看看V_send线程启的函数:

1
2
3
4
5
6
7
8
9
def V_config_server_send(self):
# port=11000
port=25600
text2=b'给节点2发送的配置报文内容'
text3=b'给节点3发送的配置报文内容'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(text2, ('192.168.3.2', port))
sock.sendto(text3,('192.168.3.3',port))
print("已发送")

再看看V_listen线程启的函数:

1
2
3
4
5
6
7
8
9
10
11
def V_config_server_receive(self):
port=11000
port=25600
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
data, address = sock.recvfrom(1024)
time.sleep(2)
self.V_num=1
self.V_if_iperf=1
# print(data)
self.V_iperf_server(self)

注意在sock.recvfrom(1024)函数之后,即收到了板子回复报文之后,执行了self.V_iperf_server(self)函数,这是在配置好了之后,开启iperf的server端。

看看这个V_iperf_server函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def V_iperf_server(self):
fontObj = pygame.font.Font(self.ttf_abs, 17)
if 'Windows'in platform.platform():#Windows系统
print('This platform is Windows')
with subprocess.Popen(["iperf-2.1.0-rc-win.exe","-s","-u","--port","24600","-i","2"], shell=True,stdout=subprocess.PIPE, universal_newlines=True) as process:
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.render.append(fontObj.render(output.strip(), False, (0, 0, 0)))
if len(self.render)>6:#实现滚动
del self.render[0]

iperf server端在接收到iperf的信息之后,暂存为output,然后转化成字体格式保存在self.render列表中

(iperf 的client端原理一样,不用赘述)

三节点组网

==95==行,点击到“三节点组网”,BK_flag=3,第三个界面

==46行,开局就打开监听==

1
2
server = Thread(target=remote.pipe_begin, args=(remote,))
server.start()

执行Remote.py中的remote类中的pipe_begin函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def pipe_begin(self):#建立pipe
parent_conn, child_conn=Pipe()

fa= multiprocessing.Process(target=self.UDP_test_server, kwargs={'self':self,'pipe':child_conn})#开启管道的发端,为UDP_test_server函数,管道的接收端就默认为自己了
fa.start()
flag=1
while True:
temp = parent_conn.recv()
temp=temp.split()

# print('receive data is:',temp)
if flag==1:


self.Pitch_angle = temp[0]
self.Yaw_angle=temp[1]
self.Roll_angle = temp[2]
#省略下面的部分代码

#现在邻接表的选择也由S状态决定
if self.s_node1=='1' and self.s_node2=='1':
self.Nighbour_v["12"]=1
self.Nighbour_s["12"]=1
if self.s_node1=='1' and self.s_node3=='1':
self.Nighbour_v["13"]=1
self.Nighbour_s["13"]=1
if self.s_node3=='1' and self.s_node2=='1':
self.Nighbour_v["23"]=1
self.Nighbour_s["23"]=1


flag+=1
elif flag==2:
flag+=1
elif flag==3:
flag==1#隔2个报文收一个,避免发的速率过快处理不来

UDP_test_server函数是执行了UDP_test.py文件中的server函数,

UDP_test.py中 的server函数:

1
2
3
4
5
6
7
def server(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
while True:
data, address = sock.recvfrom(1024)
# print(data)
node_data(data)#执行报文解析函数

node_data()#报文解析函数就是将报文中的各字段解析出来,然后print出来那些数据:

1
2
3
4
5
print(Pitch_angle_s, Yaw_angle_s, Roll_angle_s, 
Node_x_s, Node_y_s, Node_z_s, Node_x_speed_s, Node_y_speed_s, Node_z_speed_s, Node1_id,
Node2_x_s, Node2_y_s, Node2_z_s, Node2_x_speed_s, Node2_y_speed_s, Node2_z_speed_s, Node2_id,
Node3_x_s, Node3_y_s, Node3_z_s, Node3_x_speed_s, Node3_y_speed_s, Node3_z_speed_s, Node3_id,
now_node,s_info['node1']['id'],s_info['node2']['id'],s_info['node3']['id'])

这些数据被print到了管道里,被Remote.py中的pipe_begin()函数接受并保存到self中的变量里

显示部分

主要讲==190行==之后的内容:

1
2
3
4
5
6
7
8
9
if api.render != []:
# print(api.render)
loding_flag=0
for i in range(len(api.render)):
screen.blit(api.render[i], (460, 470 + 23 * i))
elif loding_flag==1:
font_loding = pygame.font.Font(api.ttf_abs, 30)
text_big1 = font.render("配置中", 1, (255, 10, 10))
screen.blit(text_big1,(700,500))

这是判断是否在配置中,如果配置结束了,开始iperf了,那么api.render变量中就会保存iperf的信息,然后就会依次显示出来:screen.blit(api.render[i], (460, 470 + 23 * i))

==222行==,在发送配置UDP报文,但是还没有收到板子的回复时,显示“等待回复中”字样

1
2
3
4
5
6
7
8
if len(api.render)>=3:
V_waiting_flag=0
elif V_waiting_flag==1:
pygame.draw.rect(screen,[200,200,200],[570,220,250,120],0)

font_loding = pygame.font.Font(api.ttf_abs, 30)
text_big1 = font.render("等待回复中", 1, (0, 0, 0))
screen.blit(text_big1,(600,250))

242行到258行是根据节点的信息,判断节点该不该点亮,255行则是在修改邻接矩阵,决定后面的连线情况

261行 为画出连线

380到388行,为显示那几个大字

390行 为刷新屏幕,因为pygame的逻辑,这是一个整个的大循环,每个循环之后都需要刷新一遍元素,重新显示

391行可以看作是屏幕刷新率

学长加油:smiley: