python多线程/进程问题:以iperf3为例与测试平台解析

3,521 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

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

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

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

server端在这里插入图片描述

client端: 在这里插入图片描述

第一个坑

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

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

第二个坑

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

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调试界面居然没有退出!!

在这里插入图片描述

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

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。简单粗暴又好用:在这里插入图片描述

感谢:雷学长

测试平台代码解析

文件结构

  • product.py 主界面

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

  • Remote.py

    • UDP_test.py

    • UDPPingerClient.py

    • 2-2-UDPPingServer.py

V段组网测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uJ3P16O-1621152582353)(C:\Users\Administrator\Desktop\QQ图片20210114230810.png)]

product.py ==77行==

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

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行==

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线程启的函数:

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线程启的函数:

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函数:

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行,开局就打开监听==

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

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

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函数:

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出来那些数据:

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行==之后的内容:

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报文,但是还没有收到板子的回复时,显示“等待回复中”字样

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: