尝试用多线程,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'))
以上功能,实现了上位机以多线程连接多个客户端,并且分别记录到本地文件的过程。