【C++/Qt】Qt 实现 WebSocket 测试工具:连接、消息收发与通信日志

本文基于 C++/Qt 实现一个 WebSocket 测试工具,支持连接 WebSocket 服务器、发送文本消息、接收服务器消息、主动断开连接、记录通信日志等功能。重点不是单纯展示代码,而是从 WebSocket 的通信特点出发,分析一个 WebSocket 客户端应该如何构思,以及如何使用 Qt 中的 QWebSocket 完成连接管理和消息收发。

一、WebSocket 是什么?为什么不用普通 HTTP?

在理解 WebSocket 之前,可以先回想一下 HTTP 的工作方式。

普通 HTTP 更像是"一问一答":

复制代码
客户端:我发一个请求给服务器
服务器:我返回一个响应给客户端

比如 GET 请求文章列表、POST 提交登录信息,这些都属于 HTTP 的典型场景。它的特点是:客户端主动发起请求,服务器被动返回结果

但是有些场景不太适合这种方式,比如:

复制代码
聊天消息
实时弹幕
在线游戏
股票价格推送
设备实时状态监控
服务器主动通知客户端

这些场景有一个共同点:服务器可能随时有新数据要发给客户端

如果用普通 HTTP,就只能让客户端不停地问服务器:

复制代码
客户端:有新消息吗?
服务器:没有

客户端:有新消息吗?
服务器:没有

客户端:有新消息吗?
服务器:有,给你

这种方式叫轮询,能用,但不够优雅,也比较浪费资源。

WebSocket 就是为了解决这类"实时双向通信"问题的。它的特点是:连接建立之后,客户端和服务器之间会保持一条长连接,双方都可以主动发送消息。

可以简单理解为:

复制代码
HTTP:打电话问一句,对方回一句,然后挂断
WebSocket:电话一直保持接通,双方可以随时说话

所以 WebSocket 更适合需要实时通信的场景。

WebSocket 地址一般有两种:

复制代码
ws://      普通 WebSocket 连接
wss://     加密 WebSocket 连接,类似 HTTPS

因此在测试工具中,URL 输入框应该要求用户输入完整的 ws://wss:// 地址。

二、实现 WebSocket 测试工具前,先构思哪些功能?

一个 WebSocket 测试工具,本质上就是一个 WebSocket 客户端。它要做的事情比 HTTP 更像"连接后持续通信"。

使用流程大概是这样:

复制代码
输入 WebSocket 地址
点击连接服务器
连接成功后发送文本消息
等待服务器返回消息
把发送和接收内容显示到通信日志中
需要时主动断开连接

所以界面上至少需要这些部分:

复制代码
1. WebSocket 地址输入框:填写 ws:// 或 wss:// 地址
2. 连接按钮:连接服务器
3. 断开按钮:主动关闭连接
4. 发送内容输入框:填写要发送的文本消息
5. 发送按钮:发送消息
6. 通信日志区域:显示连接、发送、接收、错误等信息

在 Qt 里,实现 WebSocket 客户端最核心的类是:

复制代码
QWebSocket

它可以完成连接、发送、接收和断开操作。当前 FormWebSocketTester 类中,也正是使用 QWebSocket *m_webSocket 作为 WebSocket 客户端对象,同时用 m_connected 保存连接状态,用 m_logEntries 缓存通信日志。

类中的核心成员可以这样设计:

复制代码
private:
    Ui::FormWebSocketTester *ui;

    // WebSocket 客户端对象,负责连接、发送和接收消息
    QWebSocket *m_webSocket = nullptr;

    // 当前是否已经连接服务器
    bool m_connected = false;

    // 日志缓存,用于保存通信记录
    QStringList m_logEntries;

这里的思路很清楚:

复制代码
m_webSocket:真正负责 WebSocket 通信
m_connected:判断当前能不能发送消息
m_logEntries:记录连接、发送、接收、错误信息

使用 m_connected 的原因是:WebSocket 必须先连接成功,才能发送消息。如果没有连接就允许用户点击发送,程序逻辑就会混乱。

三、初始化界面和信号槽:先把"连接状态"设计清楚

WebSocket 和 HTTP 最大的不同是:HTTP 点一次发送就结束了,而 WebSocket 有明显的"连接中"和"未连接"状态。

所以初始化界面时,要先设置默认提示,并让日志区域只读:

cpp 复制代码
void FormWebSocketTester::initUi()
{
    // 设置 WebSocket 地址输入提示
    ui->lineEdit_WebSocketUrl->setPlaceholderText(
        "例如:ws://127.0.0.1:8080 或 wss://example.com/ws"
    );

    // 设置发送内容输入提示
    ui->textEdit_WebSocketSendData->setPlaceholderText(
        "请输入要发送的WebSocket文本消息..."
    );

    // 默认填入一条测试消息,方便连接成功后直接发送
    ui->textEdit_WebSocketSendData->setPlainText("Hello WebSocket Server.");

    // 通信日志只用于显示,不允许手动编辑
    ui->textEdit->setReadOnly(true);
    ui->textEdit->setPlaceholderText("WebSocket通信日志会显示在这里...");
}

除了界面初始化,WebSocket 还非常依赖信号槽。因为连接成功、断开连接、收到消息、发生错误,这些事件都不是代码立即返回的,而是由 Qt 在对应时机发出信号。

当前实现中,按钮点击信号连接到对应槽函数,同时 QWebSocketconnecteddisconnectedtextMessageReceived 和错误信号也分别连接到了处理函数。

cpp 复制代码
void FormWebSocketTester::initConnections()
{
    // 点击连接按钮,连接 WebSocket 服务器
    connect(ui->pushButton_WebSocketConnect, &QPushButton::clicked,
            this, &FormWebSocketTester::onConnectClicked);

    // 点击断开按钮,关闭 WebSocket 连接
    connect(ui->pushButton_WebSocketDisconnect, &QPushButton::clicked,
            this, &FormWebSocketTester::onDisconnectClicked);

    // 点击发送按钮,发送文本消息
    connect(ui->pushButton_WebSocketSend, &QPushButton::clicked,
            this, &FormWebSocketTester::onSendClicked);

    // WebSocket 连接成功后触发
    connect(m_webSocket, &QWebSocket::connected,
            this, &FormWebSocketTester::onConnected);

    // WebSocket 断开连接后触发
    connect(m_webSocket, &QWebSocket::disconnected,
            this, &FormWebSocketTester::onDisconnected);

    // 收到服务器文本消息时触发
    connect(m_webSocket, &QWebSocket::textMessageReceived,
            this, &FormWebSocketTester::onTextMessageReceived);

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    // Qt6 中的错误信号
    connect(m_webSocket, &QWebSocket::errorOccurred,
            this, &FormWebSocketTester::onSocketError);
#else
    // Qt5 中的错误信号写法
    connect(m_webSocket,
            QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
            this,
            &FormWebSocketTester::onSocketError);
#endif
}

这一部分其实是 WebSocket 的核心思想之一:
不是写一个 while 循环一直等消息,而是通过信号槽等待事件发生。

也就是说:

cpp 复制代码
连接成功了,Qt 自动触发 connected
服务器发消息了,Qt 自动触发 textMessageReceived
连接断开了,Qt 自动触发 disconnected
发生错误了,Qt 自动触发 errorOccurred

这样写出来的程序更符合 Qt 的事件驱动机制。

四、连接服务器与发送消息:先建立长连接,再进行通信

连接 WebSocket 服务器时,第一步不是直接 open(),而是要先检查用户输入的地址是否合法。

比如地址不能为空,必须是合法 URL,并且协议只能是 wswss

当前实现中,连接按钮点击后,会读取 URL,检查格式和协议,如果当前 socket 不是未连接状态,还会先 abort() 中止旧连接,然后再通过 open(url) 建立新连接。

cpp 复制代码
void FormWebSocketTester::onConnectClicked()
{
    // 读取用户输入的 WebSocket 地址
    const QString urlText = ui->lineEdit_WebSocketUrl->text().trimmed();

    // 地址不能为空
    if (urlText.isEmpty()) {
        QMessageBox::warning(this, "提示", "请输入WebSocket服务器地址。");
        return;
    }

    // 转换成 QUrl,方便判断地址是否合法
    const QUrl url(urlText);

    // 必须是完整的 URL,并且包含协议和主机名
    if (!url.isValid() || url.scheme().isEmpty() || url.host().isEmpty()) {
        QMessageBox::warning(
            this,
            "提示",
            "WebSocket地址格式无效,请输入完整的 ws:// 或 wss:// 地址。"
        );
        return;
    }

    // WebSocket 只支持 ws 和 wss
    if (url.scheme() != "ws" && url.scheme() != "wss") {
        QMessageBox::warning(this, "提示", "当前仅支持 ws:// 或 wss:// 地址。");
        return;
    }

    // 如果之前还有连接状态,先强制中止旧连接
    if (m_webSocket->state() != QAbstractSocket::UnconnectedState) {
        m_webSocket->abort();
    }

    // 记录连接日志
    appendLog(QString("[%1] 正在连接:%2")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
                  .arg(urlText));

    // 连接过程中先禁用连接按钮和地址输入框,避免重复操作
    ui->pushButton_WebSocketConnect->setEnabled(false);
    ui->lineEdit_WebSocketUrl->setEnabled(false);

    // 开始连接 WebSocket 服务器
    m_webSocket->open(url);
}

这里最关键的是这一句:

cpp 复制代码
m_webSocket->open(url);

它的含义是:向指定的 WebSocket 服务器发起连接。

但是注意,调用 open(url) 并不代表立刻连接成功。它只是开始连接,真正连接成功后,会触发:

cpp 复制代码
QWebSocket::connected

然后进入:

cpp 复制代码
void FormWebSocketTester::onConnected()
{
    // 设置为已连接状态
    setConnectedState(true);

    // 记录连接成功日志
    appendLog(QString("[%1] WebSocket连接成功")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
}

连接成功后,才可以发送消息。发送消息时,也要先判断是否已经连接:

cpp 复制代码
void FormWebSocketTester::onSendClicked()
{
    // 没有连接时,不允许发送消息
    if (!m_webSocket || !m_connected) {
        QMessageBox::warning(this, "提示", "请先连接WebSocket服务器。");
        return;
    }

    // 读取用户输入的发送内容
    const QString message = ui->textEdit_WebSocketSendData->toPlainText();

    // 发送内容不能为空
    if (message.trimmed().isEmpty()) {
        QMessageBox::warning(this, "提示", "发送内容不能为空。");
        return;
    }

    // 发送文本消息,并返回发送的字节数
    const qint64 bytes = m_webSocket->sendTextMessage(message);

    // 记录发送日志
    appendLog(QString("[%1] [发送] %2 字节:%3")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
                  .arg(bytes)
                  .arg(message));
}

这里最关键的是:

cpp 复制代码
m_webSocket->sendTextMessage(message);

它表示向服务器发送一条文本消息。

WebSocket 除了可以发送文本消息,也可以发送二进制消息。不过对于一个基础测试工具来说,先实现文本消息就足够了,比如测试聊天、JSON 指令、普通字符串通信等。

五、接收消息、断开连接与日志记录:让通信过程可观察

WebSocket 是双向通信,所以除了发送消息,还要能接收服务器主动发来的消息。

当服务器发送文本消息时,Qt 会自动触发:

cpp 复制代码
textMessageReceived(const QString &message)

对应的槽函数可以这样写:

cpp 复制代码
void FormWebSocketTester::onTextMessageReceived(const QString &message)
{
    // 收到服务器消息后,直接追加到通信日志中
    appendLog(QString("[%1] [接收] %2")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
                  .arg(message));
}

这里不需要主动循环读取,因为 QWebSocket 已经帮忙完成了底层接收工作。程序只需要在收到消息时,把内容显示出来即可。

主动断开连接也很简单:

cpp 复制代码
void FormWebSocketTester::onDisconnectClicked()
{
    // 如果没有连接,直接返回
    if (!m_webSocket || m_webSocket->state() == QAbstractSocket::UnconnectedState) {
        return;
    }

    // 记录主动断开日志
    appendLog(QString("[%1] 主动断开连接")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));

    // 关闭 WebSocket 连接
    m_webSocket->close();
}

断开后会自动触发 disconnected 信号,然后进入:

cpp 复制代码
void FormWebSocketTester::onDisconnected()
{
    // 设置为未连接状态
    setConnectedState(false);

    // 记录断开日志
    appendLog(QString("[%1] WebSocket连接已断开")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
}

错误处理也很重要,比如地址错误、服务器没有启动、连接被拒绝、网络断开等情况,都应该显示到日志中:

cpp 复制代码
void FormWebSocketTester::onSocketError(QAbstractSocket::SocketError error)
{
    Q_UNUSED(error)

    // 获取错误信息
    const QString errorString = m_webSocket ? m_webSocket->errorString() : "未知错误";

    // 记录错误日志
    appendLog(QString("[%1] WebSocket错误:%2")
                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
                  .arg(errorString));

    // 出错后恢复为未连接状态
    setConnectedState(false);
}

为了让界面状态和连接状态保持一致,可以单独封装一个 setConnectedState() 函数。当前实现中,这个函数会根据连接状态启用或禁用 URL 输入框、连接按钮、断开按钮、发送按钮和消息输入框。

cpp 复制代码
void FormWebSocketTester::setConnectedState(bool connected)
{
    // 保存当前连接状态
    m_connected = connected;

    // 已连接时,不允许修改地址,也不允许再次点击连接
    ui->lineEdit_WebSocketUrl->setEnabled(!connected);
    ui->pushButton_WebSocketConnect->setEnabled(!connected);

    // 已连接时,才允许断开连接和发送消息
    ui->pushButton_WebSocketDisconnect->setEnabled(connected);
    ui->pushButton_WebSocketSend->setEnabled(connected);
    ui->textEdit_WebSocketSendData->setEnabled(connected);

    // 记录状态变化
    if (connected) {
        appendLog("状态:已连接");
    } else {
        appendLog("状态:未连接");
    }
}

最后,日志显示和日志保存也可以保留。通信日志不只是为了好看,而是为了让测试过程可追踪。

cpp 复制代码
void FormWebSocketTester::appendLog(const QString &message)
{
    // 添加到日志缓存,方便后续保存到文件
    m_logEntries.append(message);

    // 显示到界面的通信日志区域
    if (ui && ui->textEdit) {
        ui->textEdit->append(message);

        // 光标移动到最后,保证最新日志可见
        ui->textEdit->moveCursor(QTextCursor::End);
    }

    // 同时输出到调试窗口
    qDebug() << "WebSocket测试工具日志:" << message;
}

这样每一次连接、断开、发送、接收、错误都会显示在日志区域中,测试时就能清楚看到完整通信过程。

总结

WebSocket 和普通 HTTP 最大的区别在于:HTTP 更偏向"一次请求,一次响应",而 WebSocket 是"建立长连接后,双方都可以随时发送消息"。

实现一个 Qt WebSocket 测试工具,可以按照这个思路来设计:

cpp 复制代码
1. 使用 QWebSocket 作为客户端对象
2. 输入 ws:// 或 wss:// 地址
3. 调用 open(url) 连接服务器
4. 连接成功后通过 sendTextMessage() 发送文本消息
5. 通过 textMessageReceived 信号接收服务器消息
6. 通过 close() 主动断开连接
7. 使用日志区域记录连接、发送、接收和错误信息

从代码结构上看,几个关键函数分别负责不同任务:

cpp 复制代码
initUi():初始化界面提示和默认状态
initConnections():绑定按钮和 WebSocket 信号
onConnectClicked():校验地址并发起连接
onSendClicked():发送文本消息
onTextMessageReceived():接收服务器消息
setConnectedState():根据连接状态控制界面
appendLog():记录并显示通信日志

整体来看,这个 WebSocket 测试模块并不复杂,但它和 HTTP 模块的思想明显不同。HTTP 更适合接口请求测试,而 WebSocket 更适合实时通信测试。把 WebSocket 加入网络调试助手后,就可以覆盖聊天、推送、设备状态上报、实时数据返回等更多应用场景。

0voice · GitHub

相关推荐
Hello eveybody6 小时前
学习C++的好处
开发语言·c++
机器小乙6 小时前
AI客户端架构演进:从套壳插件到C++原生护城河
c++·人工智能·架构
十五年专注C++开发6 小时前
CMake基础: Qt之qt5_wrap_ui
开发语言·c++·qt·ui
南境十里·墨染春水6 小时前
C++日志 1——日志系统的概念与分类
开发语言·c++
(Charon)6 小时前
【C++/Qt】Qt 实现 HTTP 测试工具:从请求构思到 GET/POST 实现
c++·qt·http
jf加菲猫6 小时前
第16章 容器类
开发语言·c++·qt·ui
人道领域6 小时前
从零实现一个轻量级 RPC 框架:通信协议与动态代理的核心原理
开发语言·网络·qt
fish_xk6 小时前
二叉搜索树
c++
熬夜敲代码的猫6 小时前
C++:让你玩转多态
c++·多态