【QML】在C++中实现WebSocket功能并暴露给QML

要在C++中实现WebSocket功能并暴露给QML,您需要创建一个C++类来处理WebSocket连接,然后将其注册为QML类型。以下是完整的实现步骤:

1、创建C++ WebSocket处理类

WebSocketHandler.h

复制代码
 1 #ifndef WEBSOCKETHANDLER_H
 2 #define WEBSOCKETHANDLER_H
 3 
 4 #include <QObject>
 5 #include <QWebSocket>
 6 #include <QUrl>
 7 
 8 class WebSocketHandler : public QObject
 9 {
10     Q_OBJECT
11     Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
12     Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
13 
14 public:
15     explicit WebSocketHandler(QObject *parent = nullptr);
16     ~WebSocketHandler();
17 
18     Q_INVOKABLE void connectToServer(const QString &url);
19     Q_INVOKABLE void disconnectFromServer();
20     Q_INVOKABLE void sendMessage(const QString &message);
21 
22     bool isConnected() const;
23     QString statusMessage() const;
24     void setStatusMessage(const QString &message);
25 
26 signals:
27     void connectedChanged(bool connected);
28     void statusMessageChanged(const QString &message);
29     void messageReceived(const QString &message);
30     void errorOccurred(const QString &error);
31 
32 private slots:
33     void onConnected();
34     void onDisconnected();
35     void onTextMessageReceived(const QString &message);
36     void onError(QAbstractSocket::SocketError error);
37 
38 private:
39     QWebSocket *m_webSocket;
40     bool m_connected;
41     QString m_statusMessage;
42     QString m_url;
43 
44 };
45 
46 #endif // WEBSOCKETHANDLER_H

WebSocketHandler.cpp

复制代码
 1 #include "WebSocketHandler.h"
 2 #include <QDebug>
 3 
 4 WebSocketHandler::WebSocketHandler(QObject *parent)
 5     : QObject(parent)
 6     , m_webSocket(new QWebSocket)
 7     , m_connected(false)
 8     , m_statusMessage("Not connected")
 9 {
10     connect(m_webSocket, &QWebSocket::connected, this, &WebSocketHandler::onConnected);
11     connect(m_webSocket, &QWebSocket::disconnected, this, &WebSocketHandler::onDisconnected);
12     connect(m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketHandler::onTextMessageReceived);
13     connect(m_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &WebSocketHandler::onError);
14 
15 }
16 
17 WebSocketHandler::~WebSocketHandler()
18 {
19     disconnectFromServer();
20     delete m_webSocket;
21 }
22 
23 void WebSocketHandler::connectToServer(const QString &url)
24 {
25     if (m_connected) {
26         disconnectFromServer();
27     }
28 
29     m_url = url;
30     m_webSocket->open(QUrl(url));
31     setStatusMessage("Connecting...");
32 }
33 
34 void WebSocketHandler::disconnectFromServer()
35 {
36     m_webSocket->close();
37 }
38 
39 void WebSocketHandler::sendMessage(const QString &message)
40 {
41     if (!m_connected) {
42         emit errorOccurred("Not connected to server");
43         // return;
44     }
45 
46     m_webSocket->sendTextMessage(message);
47 }
48 
49 bool WebSocketHandler::isConnected() const
50 {
51     return m_connected;
52 }
53 
54 QString WebSocketHandler::statusMessage() const
55 {
56     return m_statusMessage;
57 }
58 
59 void WebSocketHandler::setStatusMessage(const QString &message)
60 {
61     if (m_statusMessage != message) {
62         m_statusMessage = message;
63         emit statusMessageChanged(m_statusMessage);
64     }
65 }
66 
67 
68 void WebSocketHandler::onConnected()
69 {
70     m_connected = true;
71     setStatusMessage("Connected to server");
72     emit connectedChanged(m_connected);
73 }
74 
75 void WebSocketHandler::onDisconnected()
76 {
77     m_connected = false;
78     setStatusMessage("Connection closed");
79     emit connectedChanged(m_connected);
80 }
81 
82 void WebSocketHandler::onTextMessageReceived(const QString &message)
83 {
84     emit messageReceived(message);
85 }
86 
87 void WebSocketHandler::onError(QAbstractSocket::SocketError error)
88 {
89     Q_UNUSED(error)
90     QString errorMsg = "Error: " + m_webSocket->errorString();
91     setStatusMessage(errorMsg);
92     emit errorOccurred(errorMsg);
93 }

2、在main.cpp中注册QML类型

复制代码
 1 #include <QGuiApplication>
 2 #include <QQmlApplicationEngine>
 3 #include <QQmlContext>
 4 #include "WebSocketHandler.h"
 5 
 6 int main(int argc, char *argv[])
 7 {
 8 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 9     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
10 #endif
11     QGuiApplication app(argc, argv);
12 
13     qmlRegisterType<WebSocketHandler>("WebSocket", 1, 0, "WebSocketHandler");
14 
15     QQmlApplicationEngine engine;
16     const QUrl url(QStringLiteral("qrc:/main.qml"));
17     QObject::connect(
18         &engine,
19         &QQmlApplicationEngine::objectCreated,
20         &app,
21         [url](QObject *obj, const QUrl &objUrl) {
22             if (!obj && url == objUrl)
23                 QCoreApplication::exit(-1);
24         },
25         Qt::QueuedConnection);
26     engine.load(url);
27 
28     return app.exec();
29 }

3、修改QML文件以使用C++ WebSocket处理类

复制代码
  1 import QtQuick          2.11
  2 import QtQuick.Controls 2.15
  3 import QtQuick.Dialogs  1.3
  4 import QtQuick.Layouts  1.11
  5 import QtQuick.Window   2.11
  6 import WebSocket 1.0
  7 
  8 
  9  Component {
 10         id: webSocketComp
 11         Popup {
 12             id: webSocketPop
 13             width: 800
 14             height: 600
 15             anchors.centerIn: parent
 16             padding: 0
 17 
 18             // WebSocket连接
 19             WebSocketHandler{
 20                 id: webSocketHandle
 21 
 22                 onMessageReceived: {
 23                     messageDisplay.append(message + "\n")
 24                 }
 25 
 26                 onErrorOccurred: {
 27                     messageDisplay.append(error + "\n")
 28                 }
 29 
 30                 onStatusMessageChanged: {
 31                     messageDisplay.append(statusMessage + "\n")
 32                 }
 33 
 34             }
 35 
 36             // 发送消息函数
 37             function sendMessage() {
 38                 if (!webSocketHandle.connected) {
 39                     messageDisplay.append("错误: 请先连接到服务器\n")
 40                     return
 41                 }
 42 
 43                 var text = messageTextField.text
 44 
 45                 if (text === "") {
 46                     messageDisplay.append("错误: 请输入消息内容\n")
 47                     return
 48                 }
 49 
 50                 webSocketHandle.sendMessage(text)
 51                 messageTextField.clear()
 52             }
 53 
 54 
 55 
 56             background: Rectangle {
 57                 anchors.fill: parent
 58                 color: "#042A3F"
 59                 radius: 8
 60                 border.color: "#00E5FF"
 61                 border.width: 1
 62             }
 63 
 64             ColumnLayout {
 65                 anchors.fill: parent
 66                 anchors.margins: 8
 67                 spacing: 8
 68 
 69                 // 标题栏
 70                 Rectangle {
 71                     Layout.fillWidth: true
 72                     Layout.preferredHeight: 36
 73                     color: "#041A2F"
 74                     radius: 4
 75 
 76                     RowLayout {
 77                         anchors.fill: parent
 78                         anchors.leftMargin: 12
 79                         anchors.rightMargin: 4
 80                         spacing: 10
 81 
 82                         Label {
 83                             text: "通信测试窗口"
 84                             font.pointSize: 12
 85                             font.bold: true
 86                             color: "white"
 87                             Layout.alignment: Qt.AlignVCenter
 88                         }
 89 
 90                         Item {
 91                             Layout.fillWidth: true
 92                         }
 93 
 94                         Button {
 95                             Layout.preferredWidth: 28
 96                             Layout.preferredHeight: 28
 97                             Layout.alignment: Qt.AlignVCenter
 98 
 99                             background: Rectangle {
100                                 anchors.fill: parent
101                                 color: parent.down ? "#E53935" :
102                                       (parent.hovered ? "#FF5252" : "white")
103                                 radius: 14
104                                 border.color: "#FFFFFF"
105                                 border.width: 1
106                             }
107 
108                             contentItem: Image {
109                                 anchors.fill: parent
110                                 anchors.margins: 6
111                                 source: "qrc:/images/close.svg"
112                                 fillMode: Image.PreserveAspectFit
113                             }
114 
115                             onClicked:{
116                                 if (webSocketHandle.connected) {
117                                     webSocketHandle.disconnectFromServer()
118                                 }
119                                 webSocketPop.close()
120                             }
121                         }
122                     }
123                 }
124 
125                 // 消息显示区域
126                 Rectangle {
127                     Layout.fillWidth: true
128                     Layout.fillHeight: true
129                     color: "white"
130                     radius: 4
131                     border.color: "#CCCCCC"
132                     border.width: 1
133 
134                     ScrollView {
135                         anchors.fill: parent
136                         anchors.margins: 4
137 
138                         TextArea {
139                             id: messageDisplay
140                             readOnly: true
141                             wrapMode: TextArea.Wrap
142                             font.pointSize: 10
143                             background: null
144                             text: "WebSocket通信测试窗口已就绪\n"
145                         }
146                     }
147                 }
148 
149                 // 表单区域
150                 GridLayout {
151                     Layout.fillWidth: true
152                     columns: 2
153                     columnSpacing: 8
154                     rowSpacing: 8
155 
156                     Label {
157                         text: "服务器URL:"
158                         font.pointSize: 10
159                         font.bold: true
160                         color: "white"
161                         Layout.alignment: Qt.AlignVCenter
162                     }
163 
164                     TextField {
165                         id: urlTextField
166                         Layout.fillWidth: true
167                         placeholderText: "请输入WebSocket服务器地址"
168                         font.pointSize: 10
169                         // ws://localhost:6789/api/v1/ws?userId=a46b2cdf-af02-4a89-b034-b8587483fd33
170                         //"wss://echo.websocket.org"
171                         text: "ws://192.168.0.105:6789/api/v1/ws?userId=a46b2cdf-af02-4a89-b034-b8587483fd33"
172                         background: Rectangle {
173                             implicitHeight: 30
174                             radius: 4
175                             color: "white"
176                             border.color: "#CCCCCC"
177                             border.width: 1
178                         }
179                     }
180 
181                     Label {
182                         text: "发送消息:"
183                         font.pointSize: 10
184                         font.bold: true
185                         color: "white"
186                         Layout.alignment: Qt.AlignVCenter
187                     }
188 
189                     TextField {
190                         id: messageTextField
191                         Layout.fillWidth: true
192                         placeholderText: "请输入要发送的消息"
193                         font.pointSize: 10
194                         background: Rectangle {
195                             implicitHeight: 30
196                             radius: 4
197                             color: "white"
198                             border.color: "#CCCCCC"
199                             border.width: 1
200                         }
201 
202                         onAccepted: {
203                             // 按回车键发送消息
204                             sendMessage()
205                         }
206                     }
207                 }
208 
209                 // 按钮区域
210                 RowLayout {
211                     Layout.fillWidth: true
212                     Layout.preferredHeight: 40
213                     spacing: 12
214 
215                     Item {
216                         Layout.fillWidth: true
217                     }
218 
219                     // 自定义按钮组件
220                     component CustomButton: Button {
221                         property alias textColor: btnText.color
222 
223                         background: Rectangle {
224                             gradient: Gradient {
225                                 GradientStop {
226                                     position: 0.0;
227                                     color: parent.down ? "#2A3F5F" :
228                                                          (parent.hovered ? "#2A3F5F" : "#1E2A4A")
229                                 }
230                                 GradientStop {
231                                     position: 1.0;
232                                     color: parent.down ? "#1E2A4A" :
233                                                          (parent.hovered ? "#1E2A4A" : "#2A3F5F")
234                                 }
235                             }
236                             radius: 6
237                             border.color: "#00E5FF"
238                             border.width: 2
239                         }
240 
241                         contentItem: Text {
242                             id: btnText
243                             text: parent.text
244                             font.bold: true
245                             font.pointSize: 10
246                             color: "#00E5FF"
247                             horizontalAlignment: Text.AlignHCenter
248                             verticalAlignment: Text.AlignVCenter
249                         }
250                     }
251 
252 
253                     CustomButton {
254                         id: connectButton
255                         text: webSocketHandle.connected ? "断开" : "连接"
256                         width: 80
257                         height: 36
258                         onClicked: {
259                             if (webSocketHandle.connected) {
260                                 webSocketHandle.disconnectFromServer()
261                             } else {
262                                 webSocketHandle.connectToServer(urlTextField.text)
263                             }
264                         }
265                     }
266 
267 
268                     CustomButton {
269                         id: sendButton
270                         text: "发送"
271                         width: 80
272                         height: 36
273                         enabled: webSocketHandle.connected
274                         onClicked: {
275                              sendMessage()
276                         }
277                     }
278 
279                     CustomButton {
280                         id: clearButton
281                         text: "清空"
282                         width: 80
283                         height: 36
284                         onClicked: {
285                             messageDisplay.clear()
286                             messageDisplay.text = "消息已清空\n"
287                         }
288                     }
289 
290                 }
291             }
292         }
293     }

在测试接口时,若没有服务器,可以使用"wss://echo.websocket.org",这是一个回调显示,即你向服务器发送什么信息,也会得到什么信息。

4、修改.pro文件以包含必要的模块

在您的.pro文件中,确保添加了websockets模块:

复制代码
QT += quick websockets