基于Qt WebSockets +nlohmann.json实现简单HTTP通信

1.引言

为什么选择Qt进行网络通信开发?

Qt 提供了一套跨平台、功能完备的网络模块,具有以下优势:

Qt 提供了一套跨平台、功能完备的网络模块,具有以下优势:

  • 集成度高 :Qt 自带了丰富的网络通信类(如 QNetworkAccessManager, QTcpSocket, QWebSocket 等),使用方便。
  • 跨平台统一 API:一次开发,兼容 Windows、Linux、macOS。
  • 信号槽机制天然适合事件驱动通信:无需手动管理线程与回调。
  • 配合 JSON 库可快速实现类 HTTP 风格通信:语义清晰、开发效率高。

为什么选择这个技术栈?

  • Qt WebSockets
    • 基于事件驱动的信号槽机制,避免复杂的线程同步;
    • 跨平台支持(Windows/Linux/macOS/嵌入式);
    • 与Qt其他模块(如GUI、数据库)无缝集成。
  • nlohmann/json
    • 单头文件、零依赖,集成成本极低;
    • 提供类似STL的直观API(如j["key"] = value);
    • 性能优于Qt的QJsonDocument(尤其在处理大型数据时)。

2.开发环境与依赖准备

在正式编码前,我们需要搭建好开发环境,并引入通信所需的核心模块与库。

2.1 基础环境准备

项目 要求版本 说明
操作系统 Windows 10/11 推荐用于桌面 Qt 开发
Qt Qt 6.5+ 必须勾选 Qt WebSockets 模块
IDE Visual Studio 2022 支持 Qt 插件与 CMake / qmake
编译器 MSVC 2022 (x64) 默认由 VS 提供,Qt 也会自动适配

nlohmann/json下载

可以直接从github上下载或通过镜像网站,这里提供github地址:

github.com/nlohmann/js...

这里只需要使用json.hpp,只下载该资源就可。

多机测试

由于需要测试跨设备通信,因此需要准备两台设备,使用VMware或其他工具搭建虚拟机即可。搭建完毕,先测试设备之间能否连通。如果是两台物理设备,直接测试即可。

ping不通怎么办?

(1)检查IP,确保设备IP为同一子网、子网掩码一致,如果不在可以手动设置IP或者连接同一路由(一般会在同一子网)。

(2)配置路由器允许跨子网通信,设置完毕,重启路由。

(3)检查防火墙,临时关闭防火墙测试。

2.2 创建项目

需要创建两个项目分别作为Server和Client,项目结构如下:

bash 复制代码
qt_http_demo/
├── client/               # 客户端项目
│   ├── client.ui
│   ├── client.h/.cpp
│   ├── client.pro
├── server/               # 服务端项目
│   ├── server.ui
│   ├── server.h/.cpp
│   ├── server.pro
└── thirdparty/           # 外部依赖
    └── json.hpp          # nlohmann/json

配置项目文件

在项目中添加Qt WebSockets模块:

右键项目,选择【属性】,【配置属性】,【Qt Project Setting】,【Qt Modules】,下拉选择【Select Modules...】,勾选【Qt WebSockets】。

引入json库:

将下载好的json.hpp放到项目所在文件,右键项目选择【添加】,【现有项...】,选择json.hpp文件。

在项目中添加该头文件,并重新命名:

c++ 复制代码
#include "json.hpp"
using json = nlohmann::json;

2.3 测试模块

测试代码,检查模块是否可以正常工作:

c++ 复制代码
 QWebSocket ws;
 json j = {
     {"msg","json"}
 };
 qDebug() << "测试通过:" << QString::fromStdString(j.dump());

如果能够正常运行,就可以写项目了。

3.服务端构建:基于WebSocket的通信响应框架

本章节我们将搭建一个简单的WebSocket服务端,支持多客户端接入。

3.1 UI界面设计

这里使用Qt设计器绘制简单服务器界面:

3.2 代码实现

.h

c++ 复制代码
#pragma once

#include <QtWidgets/QWidget>
#include "ui_WebSocketServerDemo.h"

#include<qwebsocketserver.h>
#include<qwebsocket.h>
#include<qdatetime.h>
#include<qmap.h>
#include<qlist.h>

#include "json.hpp"
using json = nlohmann::json;

class WebSocketServerDemo : public QWidget
{
    Q_OBJECT

public:
    WebSocketServerDemo(QWidget *parent = nullptr);
    ~WebSocketServerDemo();

private slots:
    void on_btnOpenServer_clicked();
    void on_btnCloseServer_clicked();
    void on_btnSendMsg_clicked();

    void onNewConnection();
    void processTextMessage(const QString& message);
    void socketDisconnected();

private:
    Ui::WebSocketServerDemoClass ui;

    QWebSocketServer* m_server;
    QList<QWebSocket*> m_clients;
    QMap<QString, QWebSocket*> m_clientMap;
    QDateTime* m_currentDateTime;

    void broadcastMessage(const QString& message, const QString& sender = "");
    void sendPrivateMessage(QWebSocket* client, const QString& message);
    void updateOnlineUsers();
};

.cpp

C++ 复制代码
#include "WebSocketServerDemo.h"
#include <QMessageBox>
#include <QNetworkInterface>

WebSocketServerDemo::WebSocketServerDemo(QWidget *parent)
    : QWidget(parent),m_server(nullptr),m_currentDateTime(new QDateTime())
{
    ui.setupUi(this);

    this->setWindowTitle("WebSocket 服务器");

    //初始化目标下拉框
    ui.comboBox_Target->addItem("所有客户端");
    ui.comboBox_Target->setCurrentIndex(0);

    // 获取无线网卡(WLAN)对应的 IPv4 地址
    foreach(const QNetworkInterface & interface, QNetworkInterface::allInterfaces()) {
        // 筛选启用的、运行中的接口,避免抓到虚拟机或禁用设备
        if (!(interface.flags() & QNetworkInterface::IsUp) ||
            !(interface.flags() & QNetworkInterface::IsRunning) ||
            (interface.flags() & QNetworkInterface::IsLoopBack)) {
            continue;
        }

        // 匹配无线网卡(根据类型或名称)
        if (interface.type() == QNetworkInterface::InterfaceType::Wifi ||
            interface.humanReadableName().contains("WLAN", Qt::CaseInsensitive) ||
            interface.humanReadableName().contains("无线", Qt::CaseInsensitive)) {

            // 遍历此接口的地址列表,找 IPv4
            for (const QNetworkAddressEntry& entry : interface.addressEntries()) {
                QHostAddress ip = entry.ip();
                if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
                    ui.lineEdit_IP->setText(ip.toString());
                    qDebug() << "使用无线网卡IP:" << ip.toString();
                    break;
                }
            }
        }
    }
    //设置默认端口为8000
    ui.lineEdit_Port->setText("8000");
}

WebSocketServerDemo::~WebSocketServerDemo()
{
    delete m_currentDateTime;
    if (m_server) {
        m_server->close();
        delete m_server;
    }
}
//开启服务
void WebSocketServerDemo::on_btnOpenServer_clicked() {
    QString ip = ui.lineEdit_IP->text();
    quint16 port = ui.lineEdit_Port->text().toUShort();
    // 清理旧服务器实例
    if (m_server) {
        m_server->close();
        delete m_server;
    }
    // 创建非加密模式的WebSocket服务器
    m_server = new QWebSocketServer("WebSocket Server", QWebSocketServer::NonSecureMode, this);

    if (m_server->listen(QHostAddress(ip), port)) {
        connect(m_server, &QWebSocketServer::newConnection,
            this, &WebSocketServerDemo::onNewConnection);
       // 更新UI状态
        ui.textEdit_SendMsg->append(QString("[系统] 服务已启动在 %1:%2").arg(ip).arg(port));
        ui.btnOpenServer->setEnabled(false);
        ui.btnCloseServer->setEnabled(true);
    }
    else {
        QMessageBox::critical(this, "错误", QString("无法启动服务: %1").arg(m_server->errorString()));
    }
}
//关闭服务
void WebSocketServerDemo::on_btnCloseServer_clicked() {
    if (m_server && m_server->isListening()) {
        // 通知所有客户端
        broadcastMessage("[系统] 服务器即将关闭");
        // 清理资源
        m_server->close();
        qDeleteAll(m_clients);
        m_clients.clear();
        m_clientMap.clear();
        ui.listWidget_OnlineUser->clear();
        // 更新UI状态
        ui.textEdit_SendMsg->append("[系统] 服务已停止");
        ui.btnOpenServer->setEnabled(true);
        ui.btnCloseServer->setEnabled(false);
    }
}

//发送消息
void WebSocketServerDemo::on_btnSendMsg_clicked() {
    QString message = ui.textEdit_SendMsg->toPlainText();
    if (message.isEmpty()) return;

    QString time = m_currentDateTime->currentDateTime().toString("yyyy-MM-dd hh:mm:ss");

    //获取当前选中的目标
    QString target = ui.comboBox_Target->currentText();
    if (!target.isEmpty() && target != "所有客户端") {
        //定点发送
        if (m_clientMap.contains(target)) {
            json privateMsg = {
                {"type","private"},
                {"timestamp",time.toStdString()},
                {"content",message.toStdString()},
                {"target",target.toStdString()}
            };
            sendPrivateMessage(m_clientMap[target], QString::fromStdString(privateMsg.dump()));
            // 记录发送日志
            ui.listWidget_RecvMsg->addItem(QString("[%1] [私信→%2] %3")
                .arg(time)
                .arg(target)
                .arg(message));
        }
        else {
            // 目标无效时的反馈
            ui.listWidget_RecvMsg->addItem(QString("[%1] [错误] 目标客户端不存在: %2")
                .arg(time)
                .arg(target));
        }
       
    }
    else
    {
        // 群发消息
        broadcastMessage(message, "服务器");

        ui.listWidget_RecvMsg->addItem(QString("[%1] [群发] %2")
            .arg(time)
            .arg(message));
    }
    ui.textEdit_SendMsg->clear();
}

//客户端连接
void WebSocketServerDemo::onNewConnection() {
    QWebSocket* client = m_server->nextPendingConnection();
    if (!client) return;
     // 连接信号槽
    connect(client, &QWebSocket::textMessageReceived, this, &WebSocketServerDemo::processTextMessage);
    connect(client, &QWebSocket::disconnected, this, &WebSocketServerDemo::socketDisconnected);
    // 生成客户端ID(IP:Port格式)
    QString clientId = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
    // 记录客户端
    m_clients.append(client);
    m_clientMap.insert(clientId, client);

    //更新目标下拉框
    ui.comboBox_Target->addItem(clientId);

    //记录连接日志
    QString time = m_currentDateTime->currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    QString msg = QString("[%1] %2 已连接").arg(time).arg(clientId);
    ui.listWidget_RecvMsg->addItem(msg);
    ui.textEdit_SendMsg->append(msg);

    updateOnlineUsers();// 刷新在线列表

    //发送欢迎消息(JSON格式)
    json welcomeMsg = {
        {"type","system"},
        {"timestamp",time.toStdString()},
        {"content","欢迎连接到WebSocket服务器"}
    };
    client->sendTextMessage(QString::fromStdString(welcomeMsg.dump()));
}

//接收客户端消息
void WebSocketServerDemo::processTextMessage(const QString& message) {
    QWebSocket* client = qobject_cast<QWebSocket*>(sender());
    if (!client)return;

    QString clientId = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
    QString time = m_currentDateTime->currentDateTime().toString("yyyy-MM-dd hh:mm:ss");

    try {
        // 解析JSON消息
        json msg = json::parse(message.toStdString());

        if (msg.contains("type") && msg["type"] == "chat") {
            QString content = QString::fromStdString(msg.value("content", ""));

            //显示在接收消息框
            QString displayMsg = QString("[%1] %2:%3").arg(time).arg(clientId).arg(content);
            ui.listWidget_RecvMsg->addItem(displayMsg);

            //处理广播请求
            if (msg.value("broadcast", false)) {
                broadcastMessage(content, clientId);
            }
        }
    }
    catch(const json::exception& e){
        // JSON解析错误处理
        QString errorMsg = QString("[%1] %2: JSON解析错误 - %3")
            .arg(time)
            .arg(clientId)
            .arg(e.what());
        ui.listWidget_RecvMsg->addItem(errorMsg);
        client->sendTextMessage(R"({"error": "Invalid message format"})");
    }
}

//客户端断开
void WebSocketServerDemo::socketDisconnected() {
    QWebSocket* client = qobject_cast<QWebSocket*>(sender());
    if (!client) return;
    // 获取客户端信息
    QString clientId = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
    // 记录断开日志
    QString time = m_currentDateTime->currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    QString msg = QString("[%1] %2 已断开连接").arg(time).arg(clientId);
    
    ui.listWidget_RecvMsg->addItem(msg);
    ui.textEdit_SendMsg->append(msg);
    // 清理资源  
    m_clients.removeAll(client);
    m_clientMap.remove(clientId);
    int index = ui.comboBox_Target->findText(clientId);
    // 从下拉框移除
    if (index != -1) {
        ui.comboBox_Target->removeItem(index);
    }
    client->deleteLater();

    updateOnlineUsers();// 刷新在线列表
}

//广播消息
void WebSocketServerDemo::broadcastMessage(const QString& message, const QString& sender) {
    QString time = m_currentDateTime->currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    json broadcastMsg = {
        {"type", "broadcast"},
        {"timestamp", time.toStdString()},
        {"content", message.toStdString()},
        {"sender", sender.toStdString()}
    };
    // 向所有有效客户端发送
    QString msgStr = QString::fromStdString(broadcastMsg.dump());
    for (QWebSocket* client : m_clients) {
        if (client->isValid()) {
            client->sendTextMessage(msgStr);
        }
    }
}

//私发消息
void WebSocketServerDemo::sendPrivateMessage(QWebSocket* client, const QString& message) {
    if (client && client->isValid()) {
        client->sendTextMessage(message);
    }
}

//更新在线列表
void WebSocketServerDemo::updateOnlineUsers() {
    ui.listWidget_OnlineUser->clear();
    for (const QString& clientId : m_clientMap.keys()) {
        ui.listWidget_OnlineUser->addItem(clientId);
    }
}

4.客户端构建与数据通信

本章将搭建一个简单的WebSocket客户端,实现与服务端的连接与数据通信,结合nlohmann/json实现数据传输。

4.1 UI界面设计

使用Qt设计器绘制简单服务器界面:

4.2代码实现

.h

c++ 复制代码
#pragma once

#include <QtWidgets/QWidget>
#include "ui_WebSocketClientDmeo.h"
#include<qwebsocket.h>
#include<qdatetime.h>

#include"json.hpp"
using json = nlohmann::json;

class WebSocketClientDmeo : public QWidget
{
    Q_OBJECT

public:
    WebSocketClientDmeo(QWidget *parent = nullptr);
    ~WebSocketClientDmeo();

private slots:
    void on_btnConnect_clicked();
    void on_btnDisconnect_clicked();
    void on_btnSendMsg_clicked();

    void onConnected();
    void onDisconnected();
    void onTextMessageReceived(const QString& message);
    void onError(QAbstractSocket::SocketError error);

private:
    Ui::WebSocketClientDmeoClass ui;
    QWebSocket* m_socket;
    QDateTime m_currentDateTime;
    bool m_isConnected;

    void updateStatus(const QString& message, bool isError = false);
    void appendMessage(const QString& message);
};

.cpp

c++ 复制代码
#include "WebSocketClientDmeo.h"
#include <qurl.h>
#include <qmessagebox.h>

WebSocketClientDmeo::WebSocketClientDmeo(QWidget *parent)
    : QWidget(parent),m_socket(nullptr),m_isConnected(false)
{
    ui.setupUi(this);

    this->setWindowTitle("WebSocket 客户端");

    // 设置默认URL:服务器IP
    ui.lineEdit_URL->setText("ws://192.168.2.45:8000");
    updateStatus("准备就绪");

    //初始状态控制
    ui.btnDisconnect->setEnabled(false);
}

WebSocketClientDmeo::~WebSocketClientDmeo()
{
    if (m_socket) {
        m_socket->deleteLater();
    }
}

// 连接按钮点击事件
void WebSocketClientDmeo::on_btnConnect_clicked() {
    if (m_isConnected) return;// 避免重复连接

    QString urlStr = ui.lineEdit_URL->text().trimmed();
    if (urlStr.isEmpty()) {
        updateStatus("URL不能为空", true); // 错误提示(红色)
        return;
    }

    QUrl url(urlStr);
    if (!url.isValid()) {
        updateStatus("无效的URL格式", true);
        return;
    }
    // 清理旧连接(如果存在)
    if (m_socket) {
        m_socket->deleteLater();
    }
    // 创建新的WebSocket连接
    m_socket = new QWebSocket();
    
    connect(m_socket, &QWebSocket::connected, this, &WebSocketClientDmeo::onConnected);
    connect(m_socket, &QWebSocket::disconnected, this, &WebSocketClientDmeo::onDisconnected);
    connect(m_socket, &QWebSocket::textMessageReceived,
        this, &WebSocketClientDmeo::onTextMessageReceived);
    connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::errorOccurred),
        this, &WebSocketClientDmeo::onError);

    updateStatus("正在连接...");
    m_socket->open(url);// 发起连接
}

// 断开连接按钮点击事件
void WebSocketClientDmeo::on_btnDisconnect_clicked() {
    if (m_socket && m_isConnected) {
        m_socket->close();// 主动关闭连接
    }
}

// 发送消息按钮点击事件
void WebSocketClientDmeo::on_btnSendMsg_clicked() {
    if (!m_isConnected || !m_socket) {
        updateStatus("未连接到服务器", true);
        return;
    }
    QString message = ui.textEdit_Send->toPlainText();
    if (message.isEmpty()) {
        updateStatus("消息内容不能为空", true);
        return;
    }

    try {
        // 构造JSON消息
        json msg = {
            {"type", "chat"}, // 消息类型
            {"timestamp", m_currentDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString()},
            {"content", message.toStdString()}// 消息内容
        };
        // 发送消息
        m_socket->sendTextMessage(QString::fromStdString(msg.dump()));
         // 在接收框显示已发送消息
        appendMessage(QString("[发送] %1").arg(message));
        ui.textEdit_Send->clear();// 清空输入框
    }
    catch (const std::exception& e) {
        updateStatus(QString("消息发送失败: %1").arg(e.what()), true);
    }
}

// 连接成功事件处理
void WebSocketClientDmeo::onConnected() {
    m_isConnected = true;
    updateStatus("已连接到服务器");
    // 更新按钮状态
    ui.btnConnect->setEnabled(false);
    ui.btnDisconnect->setEnabled(true);
    ui.lineEdit_URL->setEnabled(false);// 连接后禁用URL修改

    appendMessage("[系统] 连接已建立");
}

// 连接断开事件处理
void WebSocketClientDmeo::onDisconnected() {
    m_isConnected = false;
    updateStatus("已断开连接");
    // 恢复初始按钮状态
    ui.btnConnect->setEnabled(true);
    ui.btnDisconnect->setEnabled(false);
    ui.lineEdit_URL->setEnabled(true);

    appendMessage("[系统] 连接已关闭");
}

// 接收消息处理
void WebSocketClientDmeo::onTextMessageReceived(const QString& message) {
    try {
        // 解析JSON消息
        json msg = json::parse(message.toStdString());

        // 提取消息字段
        QString time = QString::fromStdString(msg.value("timestamp", ""));
        QString content = QString::fromStdString(msg.value("content", ""));
        QString sender = QString::fromStdString(msg.value("sender", "服务器"));

        // 格式化显示消息
        QString displayMsg;
        if (sender.isEmpty()) {
            displayMsg = QString("[%1] %2").arg(time).arg(content);
        }
        else {
            displayMsg = QString("[%1] [来自 %2] %3").arg(time).arg(sender).arg(content);
        }

        appendMessage(displayMsg);
    }
    catch (const json::exception& e) {
        appendMessage(QString("[错误] 消息解析失败: %1").arg(e.what()));
    }
}

// 错误处理
void WebSocketClientDmeo::onError(QAbstractSocket::SocketError error) {
    Q_UNUSED(error)// 未使用的参数
        updateStatus(QString("连接错误: %1").arg(m_socket->errorString()), true);
    appendMessage(QString("[错误] %1").arg(m_socket->errorString()));
}

// 更新状态栏显示
void WebSocketClientDmeo::updateStatus(const QString& message, bool isError) {
    ui.label_StatusInformation->setText(message);
    if (isError) {
        ui.label_StatusInformation->setStyleSheet("color: red;");
    }
    else {
        ui.label_StatusInformation->setStyleSheet("color: green;");
    }
}

// 添加消息到接收框
void WebSocketClientDmeo::appendMessage(const QString& message) {
    ui.textEdit_Recv->append(message);// 自动换行显示
}

5.效果演示

服务端初始界面

客户端初始界面

服务端开启服务

客户端连接服务

客户端向服务端发送内容

服务端群发内容

客户端私发内容

客户端断开连接

服务器关闭服务

6.总结

在本篇文章中,我们围绕 Qt 的网络通信能力 ,构建了一个完整的基于 WebSocket 的 HTTP 风格客户端与服务器通信示例。通过 nlohmann/json 库实现了结构化数据的传输,使得消息交互更加清晰、模块化,提升了开发效率。

整篇项目涵盖内容包括:

  • Qt 网络模块与 WebSocket 客户端/服务器的搭建
  • JSON 格式解析与封装,提升通信协议的灵活性
  • UI 交互设计,简化客户端操作逻辑
  • 多客户端接入管理,实现了私聊/群发功能
相关推荐
AgilityBaby11 分钟前
UE5打包项目设置Project Settings(打包widows exe安装包)
c++·3d·ue5·游戏引擎·unreal engine
cykaw25901 小时前
QT-JSON
qt·json
让我们一起加油好吗2 小时前
【基础算法】高精度(加、减、乘、除)
c++·算法·高精度·洛谷
Forest_HAHA2 小时前
<5>, Qt系统相关
开发语言·qt
鑫鑫向栄3 小时前
[蓝桥杯]缩位求和
数据结构·c++·算法·职场和发展·蓝桥杯
stormsha3 小时前
MCP架构全解析:从核心原理到企业级实践
服务器·c++·架构
梁下轻语的秋缘3 小时前
每日c/c++题 备战蓝桥杯(P1204 [USACO1.2] 挤牛奶 Milking Cows)
c语言·c++·蓝桥杯
鑫鑫向栄3 小时前
[蓝桥杯]外卖店优先级
数据结构·c++·算法·职场和发展·蓝桥杯
Zfox_3 小时前
【C++项目】:仿 muduo 库 One-Thread-One-Loop 式并发服务器
linux·服务器·c++·muduo库
wangyuxuan10294 小时前
AtCoder Beginner Contest 399题目翻译
开发语言·c++·算法