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

用 Qt5 实现一个 WebSocket 广播工具:三层架构设计实践
项目简介
WsBroadcaster 是一个基于 Qt5 + C++ 的 WebSocket 调试/广播工具,支持 Server 模式(监听端口、向所有客户端广播)和 Client 模式(连接远端服务器收发消息),带有自定义无边框标题栏和深色主题 UI。
架构设计
项目采用严格的三层结构,层间只允许通过 Qt signal/slot 通信,禁止跨层直接调用:
UI(MainWindow / TitleBar)↕ signal/slotBridge (WsBridge) ← UI 与 Core 的唯一边界↕ signal/slotCore(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 列表
小结
这个项目的设计收获:
- WsBridge 桥接层让 UI 与 Core 真正解耦,UI 重写不动一行 Core 代码
- 职责单一:Controller 管生命周期、ConnectionMgr 管连接表、Router 管路由,三者互不侵入
- 无状态 Router 用 signal 触发发送,可以独立测试广播/单播逻辑
