You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QWebSocket客户端无法重连延迟启动监听的QWebSocketServer问题

问题描述

我需要让QWebSocket客户端在服务器不可达时,通过QTimer发送ping/text消息实现重连,且客户端必须收到服务器的pong/text消息后才启动数据处理流程。

服务器启动后前20秒不监听入站连接(模拟连接异常),20秒后才开始监听并接受连接。

当前问题:若客户端首次尝试连接或在服务器未监听的20秒内连接失败,即使后续服务器启动监听,客户端也无法建立连接;但如果在服务器已启动监听后再运行客户端,则一切正常。目前客户端似乎陷入循环,无法发送ping/text消息,此前尝试用Worker类避免主线程冻结、子类化QWebSocket均未解决问题。

客户端原始代码
from time import sleep
import sys
from PySide6 import QtCore, QtWebSockets
from PySide6.QtCore import QUrl, QTimer
from PySide6.QtWidgets import QApplication


class Client(QtCore.QObject):
    def __init__(self, parent):
        super().__init__(parent)

        self.client = QtWebSockets.QWebSocket("", QtWebSockets.QWebSocketProtocol.Version13, None)
        self.client.open(QUrl("ws://127.0.0.1:6000"))
        self.client.textMessageReceived.connect(self.process_message)
        self.client.pong.connect(self.get_pong)
        # self.client.error.connect(self.error)

        self.date_list = ['2000-01-01']

        self.check_status_timer = QTimer()
        self.check_status_timer.timeout.connect(self.send_ping)
        self.check_status_timer.start(2000)

    # 连接建立后的 data processing
    def process(self, day):
        print(f"Day {day}")
        for i in range(10):
            print(f"Sleeping {9 - i}")
            sleep(.5)
        print("emitted")
        return True

    def create_and_run(self):
        current_date = self.date_list[0]
        result = self.process(current_date)
        if result:
            self.send_message()
        else:
            print("PROCESS FAILED")

    def process_message(self, message):
        if message == "ping" or message == "True":
            self.check_status_timer.stop()
            self.create_and_run()

    def get_pong(self):
        print("PONG")

    def send_ping(self):
        print("SENDING PING")
        self.client.ping()
        self.client.sendTextMessage("ping")

    def send_message(self):
        self.client.sendTextMessage("Client0")

    def error(self, error_code):
        print("error code: {}".format(error_code))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    client = Client(app)
    app.exec()
服务器原始代码
from PySide6 import QtCore, QtWebSockets, QtNetwork
from PySide6.QtWidgets import QApplication


class MyServer(QtCore.QObject):
    def __init__(self, parent):
        super().__init__(parent)
        self.clients = []
        self.server = QtWebSockets.QWebSocketServer(parent.serverName(), parent.secureMode(), parent)
        self.server.acceptError.connect(self.on_accept_error)
        self.server.newConnection.connect(self.on_new_connection)
        
        # 创建定时器延迟启动监听
        self.count = 0
        self.timer_listen = QtCore.QTimer()
        self.timer_listen.timeout.connect(self.make_listen)
        self.timer_listen.start(2000)
    
    def make_listen(self):
        if self.count >= 10:
            self.timer_listen.stop()
            self.server.listen(QtNetwork.QHostAddress.LocalHost, 6000)
            print(self.count, self.server.isListening())
        else:
            print(self.count, self.server.isListening())
            self.count += 1

    def on_accept_error(accept_error):
        print("Accept Error: {}".format(accept_error))

    def on_new_connection(self):
        client_connection = self.server.nextPendingConnection()
        client_connection.textMessageReceived.connect(self.process_message)
        client_connection.disconnected.connect(self.disconnect)
        self.clients.append(client_connection)

    def process_message(self, message):
        print(message)
        if message == "ping":
            self.sender().sendTextMessage("ping")

    def disconnect(self):
        if self.sender() in self.clients:
            self.clients.remove(self.sender())
            self.sender().deleteLater()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    serverObject = QtWebSockets.QWebSocketServer('My Socket', QtWebSockets.QWebSocketServer.NonSecureMode)
    server = MyServer(serverObject)
    serverObject.closed.connect(app.quit)
    app.exec()
问题分析与修复方案

核心问题

  1. 未处理连接状态变化:客户端仅在初始化时调用一次open(),首次连接失败后不会自动重试,后续ping/text消息均在未连接状态发送,无实际作用。
  2. 阻塞事件循环:process()方法中使用sleep(),直接阻塞Qt事件循环,导致定时器、信号处理全部停滞,客户端假死。
  3. 服务器代码bug:on_accept_error方法缺少self参数,运行时会抛出异常;disconnect方法名与Qt内置方法冲突,可能引发问题。

修复后的客户端代码

import sys
from PySide6 import QtCore, QtWebSockets
from PySide6.QtCore import QUrl, QTimer, Signal
from PySide6.QtWidgets import QApplication


class Client(QtCore.QObject):
    process_finished = Signal(bool)

    def __init__(self, parent):
        super().__init__(parent)
        self.client = QtWebSockets.QWebSocket("", QtWebSockets.QWebSocketProtocol.Version13, None)
        # 连接核心状态信号
        self.client.connected.connect(self.on_connected)
        self.client.disconnected.connect(self.on_disconnected)
        self.client.error.connect(self.on_error)
        self.client.textMessageReceived.connect(self.process_message)
        self.client.pong.connect(self.get_pong)

        self.date_list = ['2000-01-01']
        self.process_step = 0
        self.process_timer = QTimer()
        self.process_timer.timeout.connect(self.process_step_run)

        # 重连定时器:连接失败/断开时自动重试
        self.reconnect_timer = QTimer()
        self.reconnect_timer.setInterval(2000)
        self.reconnect_timer.timeout.connect(self.try_reconnect)

        # Ping定时器:仅在连接成功后启动
        self.ping_timer = QTimer()
        self.ping_timer.setInterval(2000)
        self.ping_timer.timeout.connect(self.send_ping)

        # 首次尝试连接
        self.try_reconnect()

    def try_reconnect(self):
        if not self.client.isValid():
            print("尝试连接服务器...")
            self.client.open(QUrl("ws://127.0.0.1:6000"))

    def on_connected(self):
        print("连接成功")
        self.reconnect_timer.stop()
        self.ping_timer.start()

    def on_disconnected(self):
        print("连接断开,启动重连")
        self.ping_timer.stop()
        self.reconnect_timer.start()

    def on_error(self, error_code):
        print(f"错误代码: {error_code}")
        self.reconnect_timer.start()

    # 用定时器分步执行处理逻辑,避免阻塞事件循环
    def start_process(self, day):
        print(f"Day {day}")
        self.process_step = 0
        self.process_timer.start(500)

    def process_step_run(self):
        if self.process_step < 10:
            print(f"Sleeping {9 - self.process_step}")
            self.process_step += 1
        else:
            self.process_timer.stop()
            print("emitted")
            self.process_finished.emit(True)

    def create_and_run(self):
        current_date = self.date_list[0]
        self.start_process(current_date)
        self.process_finished.connect(self.on_process_finished)

    def on_process_finished(self, result):
        if result:
            self.send_message()
        else:
            print("PROCESS FAILED")

    def process_message(self, message):
        if message == "ping" or message == "True":
            self.ping_timer.stop()
            self.create_and_run()

    def get_pong(self):
        print("PONG")

    def send_ping(self):
        print("SENDING PING")
        self.client.ping()
        self.client.sendTextMessage("ping")

    def send_message(self):
        self.client.sendTextMessage("Client0")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    client = Client(app)
    app.exec()

修复后的服务器代码

from PySide6 import QtCore, QtWebSockets, QtNetwork
from PySide6.QtWidgets import QApplication


class MyServer(QtCore.QObject):
    def __init__(self, parent):
        super().__init__(parent)
        self.clients = []
        self.server = QtWebSockets.QWebSocketServer(parent.serverName(), parent.secureMode(), parent)
        self.server.acceptError.connect(self.on_accept_error)
        self.server.newConnection.connect(self.on_new_connection)
        
        # 创建定时器延迟启动监听
        self.count = 0
        self.timer_listen = QtCore.QTimer()
        self.timer_listen.timeout.connect(self.make_listen)
        self.timer_listen.start(2000)
    
    def make_listen(self):
        if self.count >= 10:
            self.timer_listen.stop()
            self.server.listen(QtNetwork.QHostAddress.LocalHost, 6000)
            print(self.count, self.server.isListening())
        else:
            print(self.count, self.server.isListening())
            self.count += 1

    def on_accept_error(self, accept_error):  # 补全self参数
        print(f"Accept Error: {accept_error}")

    def on_new_connection(self):
        client_connection = self.server.nextPendingConnection()
        client_connection.textMessageReceived.connect(self.process_message)
        client_connection.disconnected.connect(self.disconnect_client)  # 重命名方法避免冲突
        self.clients.append(client_connection)

    def process_message(self, message):
        print(message)
        if message == "ping":
            self.sender().sendTextMessage("ping")

    def disconnect_client(self):
        client = self.sender()
        if client in self.clients:
            self.clients.remove(client)
            client.deleteLater()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    serverObject = QtWebSockets.QWebSocketServer('My Socket', QtWebSockets.QWebSocketServer.NonSecureMode)
    server = MyServer(serverObject)
    serverObject.closed.connect(app.quit)
    app.exec()

关键修改说明

  1. 连接状态自动化管理:新增reconnect_timer,在连接失败或断开时自动重试;连接成功后切换为ping_timer发送心跳,确保仅在有效连接时发送ping。
  2. 非阻塞数据处理:将原process()中的sleep()替换为QTimer分步执行,彻底避免阻塞Qt事件循环,保证客户端响应正常。
  3. 修复服务器bug:补全on_accept_error的self参数,重命名disconnect方法避免与Qt内置方法冲突。
  4. 信号驱动流程:用process_finished信号触发后续消息发送,完全遵循Qt的信号槽机制,避免同步阻塞。

内容的提问来源于stack exchange,提问作者Masquerade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 00:23:10