上位机设计为基于tcp的socket客户端,由于是基于QT框架的,所以将其设计为继承QObject的类,以方便使用QT的信号槽和QThread。
一、定义一个可以循环工作的Qobject类,这个类将作为Worker在子线程中工作
python
class ThreadWorker(QObject):
_signal_start = Signal() # 开始运行的信号
_signal_stop = Signal() # 停止信号
_signal_run_now = Signal() # 打破定时器周期立即运行一次
def __init__(self,target, looping, loop_interval=0, *args, **kwargs):
"""
:param target: 需要在子线程中运行的目标函数
:param looping: 是周期运行还是一次性任务
:param loop_interval: 周期运行的间隙时间
:param args: 附带参数
:param kwargs:
"""
super().__init__()
self._loop_timer = None # 循环运行定时器
self.looping = looping # 是否循环运行
self.loop_interval = loop_interval # 循环运行间隔
self.target = target # 目标函数
self.args = args
self.kwargs = kwargs
self.running = True # 是否运行
self.started = False # 已经开始
self._signal_start.connect(self._start)
self._signal_stop.connect(self._stop)
self._signal_run_now.connect(self._run_now)
@Slot()
def _start(self):
self.target(*self.args, **self.kwargs)
if self.looping:
if self._loop_timer is None:
self._loop_timer = QTimer()
self._loop_timer.setInterval(self.loop_interval)
self._loop_timer.timeout.connect(self._run_target)
if not self.started:
self._loop_timer.start()
self.started = True
@Slot()
def _stop(self):
if self._loop_timer:
self._loop_timer.stop()
self.started = False
# 立即运行一次(打破定时器,立即运行)
@Slot()
def _run_now(self):
if not self.looping:
self.target(*self.args, **self.kwargs)
return
if self._loop_timer and self._loop_timer.isActive():
self._run_target()
def start(self):
if not self.started:
self.running = True
self._signal_start.emit()
def stop(self):
self.running = False
self._signal_stop.emit()
def run_now(self):
self._signal_run_now.emit()
@Slot()
def _run_target(self):
self._loop_timer.stop() # 停止定时器,防止信号堆积
if self.running:
self.target(*self.args, **self.kwargs)
self._loop_timer.start() # 重启定时器
这是一个基于信号槽机制的Worker类,但是对外接口隐藏了信号和槽,信号与槽均设计为私有,方便使用。
- 这个Worker类的使用过程为:
- 创建子线程实例;
- 创建Worker实例;
- woker实例.moveToThread(子线程实例),将Worker移动到子线程中工作;
- 子线程.start();
- Worker.start()。
在项目的生命周期内,子线程实例和Worker实例都是存活的,子线程也是一直处于运行状态,通过控制Worker实例内的定时器的运行和停止来控制Worker实例的运行和暂停。这样做的好处是不用考虑频繁的连接、断线等操作带来的OBject生命周期管理风险。
二、将上位机定义为TCP客户端
python
# TCP客户端
class TcpClient(QObject):
def __init__(self, ip, port):
super().__init__()
self.ip = ip
self.port = port
self.socket = None
self.is_connected = False
self.lock = QMutex() #线程锁
self.send_bytes = HEART_CODE # 发送的字节
############## 创建线程和worker ################
# 连接服务器
self.connect_thread = QThread() # 线程
self.connect_worker = ThreadWorker(target=self.connect_server,
looping=True,
loop_interval=RECONNECT_INTERVAL) # worker
self.connect_worker.moveToThread(self.connect_thread) # 把worker移动到线程中
self.connect_thread.start()
# 发送数据
self.send_thread = QThread() # 线程
self.send_worker = ThreadWorker(target=self.send_data,
looping=True,
loop_interval=HEARTBEAT_INTERVAL) # worker
self.send_worker.moveToThread(self.send_thread) # 把worker移动到线程中
self.send_thread.start() # 启动线程
# 接收数据
self.recv_thread = QThread() # 线程
self.recv_worker = ThreadWorker(target=self.recv_data, looping=True) # worker
self.recv_worker.moveToThread(self.recv_thread) # 把worker移动到线程中
self.recv_thread.start() # 启动线程
def connect_server(self):
"""建立 TCP 连接"""
if self.is_connected:
return
try:
self.send_worker.stop()
self.recv_worker.stop()
# 创建 TCP socket
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(5)
self.socket.connect((self.ip, self.port))
self.is_connected = True
self.connect_worker.stop()
print(f"✅ 成功连接到服务器 {self.ip}:{self.port}")
# 启动发送工作者
self.send_worker.start()
# 启动接收工作者
self.recv_worker.start()
except Exception as e:
print(f"❌ 连接失败,{RECONNECT_INTERVAL}秒后重试... 错误:{e}")
self.is_connected = False
def send_data(self):
"""发送数据"""
if not self.is_connected:
print("⚠️ 未连接,无法发送")
return False
try:
with QMutexLocker(self.lock):
"""发送心跳包"""
if self.send_bytes == b'':
self.send_bytes = HEART_CODE
# 发送数据
self.socket.send(self.send_bytes)
print(f"📤 已发送:{self.send_bytes}")
self.send_bytes = b''
return True
except Exception as e:
print(f"❌ 发送失败:{e}")
self.is_connected = False
self.send_worker.stop()
self.recv_worker.stop()
self.connect_worker.start()
return False
def recv_data(self):
"""接收数据(处理半包/粘包)"""
if not self.is_connected:
print("⚠️ 未连接,无法接收")
return False
buffer = b''
try:
data = self.socket.recv(1024)
if not data:
print("🔌 服务器断开连接")
self.is_connected = False
self.send_worker.stop()
self.recv_worker.stop()
self.connect_worker.start()
return False
buffer += data
print(f"📥 收到数据:{buffer}")
except socket.timeout:
return True
except Exception as e:
print(f"❌ 接收异常:{e}")
self.recv_worker.stop()
self.is_connected = False
self.connect_worker.start()
def start(self):
"""启动客户端"""
print(f"🚀 启动 TCP 客户端,目标:{self.ip}:{self.port}")
self.connect_worker.start()
def msg_handler(a,b,msg):
print(msg)
def send_a_data():
with QMutexLocker(client.lock):
client.send_bytes = b'123456'
client.send_worker.run_now()
# ===================== 使用示例 =====================
if __name__ == '__main__':
app = QApplication(sys.argv)
qInstallMessageHandler(msg_handler)
client = TcpClient(SERVER_IP, SERVER_PORT)
client.start()
btn1 = QPushButton('发送自定义')
btn1.clicked.connect(send_a_data)
btn2 = QPushButton('退出应用')
btn2.clicked.connect(app.quit)
form = QWidget()
layout = QVBoxLayout(form)
layout.addWidget(btn1)
layout.addWidget(btn2)
form.show()
sys.exit(app.exec())
三、功能测试

