尝试用多线程,socket网络编程,心跳检测机制,实时绘图,丢包检测机制,校验位检测,超时重传机制,  数据库存储等功能优化项目


多线程与socket编程:

参考链接:

 Python多线程socket通信

https://www.runoob.com/python3/python3-multithreading.html  Python多线程

首先,需要明白的是socket的accept和recv这两个方法是阻塞线程的。这就意味着我们需要新开线程来处理这两个方法。

具体的程序流程大概是这样的:

1.新开一个线程用于接收新的连接(socket.accept())

2.当有新的连接时,再新开一个线程,用于接收这个连接的消息(socket.recv())

3.主线程做为控制台,接收用户的输入,进行其他操作

也就是说,服务端需要为每一个连接创建一个线程。

服务端(上位机)功能:

1 创建接口

2 接收多客户端数据

3 分别记录到本地txt

import serial
import socket  # 导入 socket 模块
from threading import Thread
import os
import time

ADDRESS = ('127.0.0.1', 8712)  # 绑定地址

g_socket_server = None  # 负责监听的socket

g_conn_pool = []   # 连接池

def init():
    """
    初始化服务端
    """
    global g_socket_server
    g_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建 socket 对象
    g_socket_server.bind(ADDRESS)
    g_socket_server.listen(5)  # 最大等待数(有很多人理解为最大连接数,其实是错误的)
    print("服务端已启动,等待客户端连接...")


def accept_client():
    """
    接收新连接
    """
    while True:
        client, _ = g_socket_server.accept()  # 阻塞,等待客户端连接
        # 加入连接池
        g_conn_pool.append(client)
        # 给每个客户端创建一个独立的线程进行管理
        thread = Thread(target=message_handle, args=(client,))
        # 设置成守护线程
        thread.setDaemon(True)
        thread.start()

def message_handle(client):
    """
    消息处理
    """
    client.sendall("连接服务器成功!".encode(encoding='utf8'))
    Time = time.strftime("%Y-%m-%d") + '设备'+str(g_conn_pool.index(client)) # 记录当前时间
    file_write_obj = open(Time + ".txt", 'w')  # 建立记录文件
    file_write_obj.writelines("=================================================")
    file_write_obj.write('\n')
    file_write_obj.writelines(time.strftime("%Y-%m-%d  %X "))
    file_write_obj.write('\n')
    file_write_obj.writelines("===============================================")
    file_write_obj.write('\n')
    while True:
        #bytes = ''
        bytes = client.recv(1024)
       # print("客户端消息:", bytes.decode(encoding='utf8'))
        file_write_obj.writelines(str(bytes.decode(encoding='utf8'))) #记录到本地
        file_write_obj.write('\n') 
        if len(bytes) == 0:
            client.close()
            # 删除连接
            g_conn_pool.remove(client)
            print("有一个客户端下线了。")
            file_write_obj.close()
            break
            

if __name__ == '__main__':  # 主函数
    init()
    # 新开一个线程,用于接收新连接
    thread = Thread(target=accept_client)
    thread.setDaemon(True)
    thread.start()
    # 主线程逻辑
    while True:
        cmd = input("""--------------------------
输入1:查看当前在线人数
输入2:给指定客户端发送消息
输入3:关闭服务端
                    """)
        if cmd == '1':
            print("--------------------------")
            print("当前在线人数:", len(g_conn_pool))
        elif cmd == '2':
            print("--------------------------")
            while (1):
                index, msg = input("请输入“索引,消息”的形式:").split(",")
                g_conn_pool[int(index)].sendall(msg.encode(encoding='utf8'))
        elif cmd == '3':
            exit()

客户端(Arm板)功能:

1 数据的处理

2 数据的回传

import socket  # 导入 socket 模块
import serial

ACCData = [0.0] * 8
GYROData = [0.0] * 8
AngleData = [0.0] * 8  # 定义三个数组,分别存储加速度角速度与角度的值

FrameState = 0  # 通过0x后面的值判断属于哪一种情况
Bytenum = 0  # 读取到这一段的第几位
CheckSum = 0  # 求和校验位


def DueData(inputdata):  # 新增的核心程序,对读取的数据进行划分,各自读到对应的数组里
    global FrameState  # 在局部修改全局变量,要进行global的定义
    global Bytenum
    global CheckSum
    result = []
    for data in inputdata:  # 在输入的数据进行遍历
        if FrameState == 0:  # 当未确定状态的时候,进入以下判断
            if data == 0x55 and Bytenum == 0:  # 0x55位于第一位时候,开始读取数据,增大bytenum
                CheckSum = data
                Bytenum = 1
                continue
            elif data == 0x51 and Bytenum == 1:  # 在byte不为0 且 识别到 0x51 的时候,改变frame
                CheckSum += data
                FrameState = 1
                Bytenum = 2
            elif data == 0x52 and Bytenum == 1:  # 同理
                CheckSum += data
                FrameState = 2
                Bytenum = 2
            elif data == 0x53 and Bytenum == 1:
                CheckSum += data
                FrameState = 3
                Bytenum = 2
        elif FrameState == 1:  # acc    #已确定数据代表加速度
            if Bytenum < 10:  # 读取8个数据
                ACCData[Bytenum - 2] = data  # 从0开始
                CheckSum += data
                Bytenum += 1
            else:
                if data == (CheckSum & 0xff):  # 假如校验位正确
                    #s.send(get_acc(ACCData).encode('utf8'))
                    result.append(get_acc(ACCData))
                CheckSum = 0  # 各数据归零,进行新的循环判断
                Bytenum = 0
                FrameState = 0
        elif FrameState == 2:  # gyro
            if Bytenum < 10:
                GYROData[Bytenum - 2] = data
                CheckSum += data
                Bytenum += 1
            else:
                if data == (CheckSum & 0xff):
                    #s.send(get_gyro(GYROData).encode('utf8'))
                    result.append(get_gyro(GYROData))
                CheckSum = 0
                Bytenum = 0
                FrameState = 0
        elif FrameState == 3:  # angle
            if Bytenum < 10:
                AngleData[Bytenum - 2] = data
                CheckSum += data
                Bytenum += 1
            else:
                if data == (CheckSum & 0xff):
                    #s.send(get_angle(AngleData).encode('utf8'))
                    result.append(get_angle(AngleData))
                CheckSum = 0
                Bytenum = 0
                FrameState = 0
    return result


def get_acc(datahex):  # 加速度
    axl = datahex[0]
    axh = datahex[1]
    ayl = datahex[2]
    ayh = datahex[3]
    azl = datahex[4]
    azh = datahex[5]

    k_acc = 16

    acc_x = (axh << 8 | axl) / 32768 * k_acc
    acc_y = (ayh << 8 | ayl) / 32768 * k_acc
    acc_z = (azh << 8 | azl) / 32768 * k_acc
    if acc_x >= k_acc:
        acc_x -= 2 * k_acc
    if acc_y >= k_acc:
        acc_y -= 2 * k_acc
    if acc_z >= k_acc:
        acc_z -= 2 * k_acc
    resa = [str(acc_x), str(acc_y), str(acc_z)]
    return 'ACC:'+ ' '.join(resa)


def get_gyro(datahex):  # 陀螺仪
    wxl = datahex[0]
    wxh = datahex[1]
    wyl = datahex[2]
    wyh = datahex[3]
    wzl = datahex[4]
    wzh = datahex[5]
    k_gyro = 2000

    gyro_x = (wxh << 8 | wxl) / 32768 * k_gyro
    gyro_y = (wyh << 8 | wyl) / 32768 * k_gyro
    gyro_z = (wzh << 8 | wzl) / 32768 * k_gyro
    if gyro_x >= k_gyro:
        gyro_x -= 2 * k_gyro
    if gyro_y >= k_gyro:
        gyro_y -= 2 * k_gyro
    if gyro_z >= k_gyro:
        gyro_z -= 2 * k_gyro
    resg = [str(gyro_x), str(gyro_y),str(gyro_z)]
    return 'GYRO:'+ ' '.join(resg)

def get_angle(datahex):  # 角度
    rxl = datahex[0]
    rxh = datahex[1]
    ryl = datahex[2]
    ryh = datahex[3]
    rzl = datahex[4]
    rzh = datahex[5]
    k_angle = 180

    angle_x = (rxh << 8 | rxl) / 32768 * k_angle
    angle_y = (ryh << 8 | ryl) / 32768 * k_angle
    angle_z = (rzh << 8 | rzl) / 32768 * k_angle
    if angle_x >= k_angle:
        angle_x -= 2 * k_angle
    if angle_y >= k_angle:
        angle_y -= 2 * k_angle
    if angle_z >= k_angle:
        angle_z -= 2 * k_angle

    resan = [str(angle_x), str(angle_y), str(angle_z)]
    return 'ANGLE:'+ ' '.join(resan)

if __name__ == '__main__':  # 主函数
    ser = serial.Serial("com5", 115200, timeout=0.5)  # 打开端口,改到循环外
    print(ser.is_open)
    s = socket.socket()  # 创建 socket 对象
    s.connect(('127.0.0.1', 8712))
    print(s.recv(1024).decode(encoding='utf8'))
    while True:
        datahex = ser.read(33)  # 不用hex()转化,直接用read读取的即是16进制
        news ='  |  '.join(DueData(datahex))
        s.send(news.encode('utf8'))

以上功能,实现了上位机以多线程连接多个客户端,并且分别记录到本地文件的过程。