基于QtPy (PySide6) 的PLC-HMI工程项目(七)上位机通信部分的初步建设:socket客户端

上位机设计为基于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类的使用过程为:
  1. 创建子线程实例;
  2. 创建Worker实例;
  3. woker实例.moveToThread(子线程实例),将Worker移动到子线程中工作;
  4. 子线程.start();
  5. 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())

三、功能测试

相关推荐
送外卖的CV工程师1 小时前
STM32 CubeMX Makefile 工程编译 入门指南
stm32·单片机·嵌入式硬件·学习·makefile·stm32cubemx
2401_873479401 小时前
金融风控中IP地址查询如何识别异常登录?IP离线库提升欺诈拦截准确率的完整指南
服务器·网络·php
喜欢吃燃面1 小时前
Linux 进程间通信:命名管道与 System V 共享内存深度解析
linux·运维·服务器·学习
神秘剑客_CN1 小时前
python安装requests及pandas
开发语言·python·pandas
绛橘色的日落(。・∀・)ノ1 小时前
机器学习笔记
笔记
人工智能AI技术1 小时前
Python 循环基础:for、while、break、continue
python
Proxy_ZZ02 小时前
不同VLAN之间怎么通信?从“隔墙喊话”到“路由器搭桥”
网络·智能路由器
hef2882 小时前
如何查找SQL字符串中字符数_掌握CHAR_LENGTH应用
jvm·数据库·python
特长腿特长2 小时前
systemd 服务配置文件,xxx.service 编辑指南,自定义我们自己的服务。
linux·网络·云原生