websoket测试工具WsBroadcaster

github地址:https://github.com/johnjiamzhong-project/WsBroadcaster

1776435261955


用 Qt5 实现一个 WebSocket 广播工具:三层架构设计实践

项目简介

WsBroadcaster 是一个基于 Qt5 + C++ 的 WebSocket 调试/广播工具,支持 Server 模式(监听端口、向所有客户端广播)和 Client 模式(连接远端服务器收发消息),带有自定义无边框标题栏和深色主题 UI。


架构设计

项目采用严格的三层结构,层间只允许通过 Qt signal/slot 通信,禁止跨层直接调用:

  UI(MainWindow / TitleBar)
    ↕ signal/slot
  Bridge    (WsBridge)          ← UI 与 Core 的唯一边界
    ↕ signal/slot
  Core(WsController / MessageRouter / ConnectionMgr)
    ↕
  Network(WsServer / WsClient)

这个分层的核心价值是可测试性和可替换性——每一层都可以独立 mock,UI 重构不影响 Core,网络库换掉不影响业务逻辑。


核心组件

WsBridge:UI 与 Core 的防腐层

// UI 侧只持有 WsBridge,不知道 WsController/WsServer 的存在

  class WsBridge : public QObject {
  public slots:
      void startServer(quint16 port);
      void sendToAll(const QString &message);
      void disconnectRemoteClient(QWebSocket *socket);
  signals:
      void serverStarted(quint16 port);
      void remoteClientConnected(QWebSocket *socket, const ConnectionInfo &info);
      void clientMessageReceived(const QString &message);
      // ...
  };

WsBridge 做的事:接收 UI 指令 → 转发给 Core;Core 事件 → 转发给 UI。它本身没有业务逻辑,只是一个类型安全的信号总线。

WsController:模式生命周期管理

  enum class WsMode { None, Server, Client };

  class WsController : public QObject {
      WsMode m_mode = WsMode::None;
      WsServer *m_server = nullptr;
      WsClient *m_client = nullptr;

      bool startServer(quint16 port);  // 切换到 Server 模式,自动 teardown 旧实例
      bool connectClient(const QUrl &url);
  };

WsController 保证 Server 和 Client 模式互斥,切换时先 teardown() 清理旧实例,再构建新实例。它不做路由,不管连接表,只负责模式切换和基础收发。

ConnectionMgr:连接表的单一真实来源

  struct ConnectionInfo {
      QString address;
      quint16 port = 0;
      QDateTime connectedAt;
  };

  class ConnectionMgr : public QObject {
      QHash<QWebSocket*, ConnectionInfo> m_connections;
  public:
      void addConnection(QWebSocket *socket);
      void removeConnection(QWebSocket *socket);
      QList<QWebSocket*> connections() const;
      int count() const;
  signals:
      void connectionAdded(QWebSocket *socket, const ConnectionInfo &info);
      void connectionRemoved(QWebSocket *socket, const ConnectionInfo &info);
  };

只要涉及"当前有哪些客户端连接",查 ConnectionMgr,不查其他地方。这是 Single Source of Truth 原则的具体落地。

MessageRouter:无状态消息分发

  class MessageRouter : public QObject {
  public slots:
      void broadcastAll(const QString &message);  // 广播给所有客户端
      void unicast(QWebSocket *socket, const QString &message);
      void clientSend(const QString &message);
  signals:
      void doSendToAll(const QString &message);
      void doSendTo(QWebSocket *socket, const QString &message);
      void doClientSend(const QString &message);
  };

MessageRouter 不持有任何 socket 对象,通过发出信号触发实际网络发送。无状态设计让它可以独立单测。


自定义无边框标题栏

为了配合深色主题,移除了系统原生标题栏,自己实现拖拽、最大化、双击还原:

  class TitleBar : public QWidget {
      void mousePressEvent(QMouseEvent *event) override;
      void mouseMoveEvent(QMouseEvent *event) override;
      void mouseDoubleClickEvent(QMouseEvent *event) override;
      // nativeEvent 处理 WM_NCHITTEST 保留系统窗口吸附/Aero Snap 行为
  };

关键点:Qt::FramelessWindowHint 去掉边框后,需要在 MainWindow::nativeEvent 里响应 WM_NCHITTEST,才能保留 Windows 的窗口吸附、贴边等原生行为。


构建配置

  cmake -B build -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_PREFIX_PATH="E:/Qt/Qt5.14.2/5.14.2/msvc2017_64"
  cmake --build build

注意点:

  • MSVC + 中文注释:CMakeLists 里必须加 /utf-8 编译选项,否则会出现级联 C2065 错误
  • 每新增一个 QObject 子类,必须放在独立的 .h/.cpp 中,确保 AUTOMOC 正确生成 moc_*.cpp
  • 新文件需同步添加到 CMakeLists.txt 的 add_executable 列表

小结

这个项目的设计收获:

  1. WsBridge 桥接层让 UI 与 Core 真正解耦,UI 重写不动一行 Core 代码
  2. 职责单一:Controller 管生命周期、ConnectionMgr 管连接表、Router 管路由,三者互不侵入
  3. 无状态 Router 用 signal 触发发送,可以独立测试广播/单播逻辑
posted @ 2026-04-17 22:17  rambos1996  阅读(5)  评论(0)    收藏  举报