Qt开发 | Qt http编程 | Qt http 相关类 | 用户登录注册功能实现 | libcurl编译与基本使用 | Qt websocket的基本使用

一、http简介

参考:HTTP教程

  HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP协议是用于从万维网服务器传输超文本到本地浏览器的传送协议。HTTP 是万维网(WWW)的数据通信的基础,设计目的是确保客户端与服务器之间的通信,是互联网上最常用的协议之一。HTTP 是一个基于 TCP/IP 通信协议来传递数据的(HTML 文件、图片文件、查询结果等)。

HTTP的请求与响应

  HTTP 的基本工作原理是客户端(通常是 web 浏览器)向服务器发送请求,服务器接收到请求后,返回相应的资源。这些资源可以是网页、图像、音频文件、视频等。HTTP 使用了客户端-服务器模型,其中客户端发送请求,服务器返回响应。

image-20240706211428906

HTTP 的请求-响应模型通常由以下几个步骤组成:

  • 建立连接:客户端与服务器之间建立连接。

    在传统的 HTTP 中,这是基于 TCP/IP 协议的。最近的 HTTP/2 和 HTTP/3 则使用了更先进的传输层协议,例如基于 TCP 的二进制协议(HTTP/2)或基于 UDP 的 QUIC 协议(HTTP/3)。

  • 发送请求:客户端向服务器发送请求,请求中包含要访问的资源的 URL、请求方法(GET、POST、PUT、DELETE 等)、请求头(例如,Accept、User-Agent)以及可选的请求体(对于 POST 或 PUT 请求)。

  • 处理请求:服务器接收到请求后,根据请求中的信息找到相应的资源,执行相应的处理操作。这可能涉及从数据库中检索数据、生成动态内容或者简单地返回静态文件。

  • 发送响应:服务器将处理后的结果封装在响应中,并将其发送回客户端。响应包含状态码(用于指示请求的成功或失败)、响应头(例如,Content-Type、Content-Length)以及可选的响应体(例如,HTML 页面、图像数据)。

  • 关闭连接:在完成请求-响应周期后,客户端和服务器之间的连接可以被关闭,除非使用了持久连接(如 HTTP/1.1 中的 keep-alive)。

HTTP方法

  HTTP 方法指定了客户端可以对服务器上的资源执行哪些动作。

主要的HTTP方法有:

  • GET:请求从服务器获取指定资源。这是最常用的方法,用于访问页面。
  • POST:请求服务器接受并处理请求体中的数据,通常用于表单提交。
  • PUT:请求服务器存储一个资源,并用请求体中的内容替换目标资源的所有内容。
  • DELETE:请求服务器删除指定的资源。
  • HEAD:与 GET 类似,但不获取资源的内容,只获取响应头信息。

HTTP状态码

  HTTP状态码是服务器对客户端请求的响应。

状态码分为五类:

  • 1xx(信息性状态码):表示接收的请求正在处理。
  • 2xx(成功状态码):表示请求正常处理完毕。
  • 3xx(重定向状态码):需要后续操作才能完成这一请求。
  • 4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
  • 5xx(服务器错误状态码):服务器在处理请求的过程中发生了错误。

安全性

  HTTP 本身是不安全的,因为传输的数据未经加密,可能会被窃听或篡改。为了解决这个问题,引入了 HTTPS,即在 HTTP 上加入 SSL/TLS 协议,为数据传输提供了加密和身份验证。

HTTP 的 URL 是由 http:// 起始与默认使用端口 80,而 HTTPS 的 URL 则是由 https:// 起始与默认使用端口443

C++ web框架有:

二、后端API测试工具postman使用介绍

  Postman 是一个流行的 API(应用程序编程接口)开发工具,它提供了一个用户友好的界面来测试、开发和调试 API。接口一般都使用这个工具测试。

postman安装

下载地址:https://www.postman.com/downloads/

  双击后即可安装,安装完成后,首次打开的界面需要注册,直接关闭,再打开就不用注册了。

image-20240706215025534

image-20240706215050306

  Postman请求后端API,例如:用户登陆127.0.0.1:8080/login,设置rawdata json,发送post请求,返回json数据。

image-20240706215746328

  Postman生成客户端请求代码,

image-20240706220500979

  请求百度

image-20240706220751800

三、Qt http相关类

1.QNetworkAccessManager

  QNetworkAccessManager 类允许应用程序发送网络请求并接收响应。

Header:
#include <QNetworkAccessManager> 
qmake:
QT += network

image-20240707155329973

详细内容可参考:https://doc.qt.io/qt-5/qnetworkaccessmanager.html

2.QNetworkRequest

  QNetworkRequest 类持有一个请求,该请求将通过 QNetworkAccessManager 发送。

Header:  
#include <QNetworkRequest>
qmake: 
QT += network

详细内容可参考:https://doc.qt.io/qt-5/qnetworkrequest.html

设置请求头

//设置url
QString url = "http://127.0.0.1:8080/login";

//设置头信息
QNetworkRequest requestInfo;
requestInfo.setUrl(QUrl(url));
requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));

获取请求结果

  发送post请求后,链接finished信号,在槽函数里获取请求结果

//发送post请求
QNetworkReply* reply = pHttpMgr->post(requestInfo, byte_array);

if (reply)
{
    //添加事件循环机制,返回后再运行后面的
    connect(pHttpMgr, &QNetworkAccessManager::finished, this, &login_register::post_requestFinished)
}

设置请求超时

  一个post请求在1ms内是大概可以完成的,将超时时间设置为1ms。这段代码的目的是同步执行网络请求。它通过 QEventLoop 等待网络请求完成或超时。如果请求在1秒内完成,finished() 信号会触发并退出事件循环;如果1秒内没有完成,超时的 quit() 信号会触发并退出事件循环。这样可以避免程序在等待网络响应时阻塞其他操作。

//添加超时处理,1ms超时
QEventLoop eventloop;
connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));

//比如设置1ms内完成请求,否则就认为是超时
QTimer::singleShot(1, &eventloop, &QEventLoop::quit);
eventloop.exec();

四、用户登陆注册功能实现

  一般客户端的登陆,注册都是请求后端接口完成的,前端不用处理用户信息,数据库之类的问题,只要把界面做的ok就可以。使用Qt提供的http相关接口,可以实现用户注册、登陆等问题。

示例:

login_register.h

#pragma once

#include <QtWidgets/QDialog>
#include "ui_login_register.h"
#include <QNetworkReply>

class login_register : public QDialog
{
    Q_OBJECT

public:
    login_register(QWidget *parent = Q_NULLPTR);

private:
    void test_http_post();         //http post请求服务器接受并处理请求体中的数据
    void test_timeout_http_post(); //http post并添加超时处理

private slots:
    void on_btnLogin_clicked();
    void post_requestFinished(QNetworkReply* reply);    //解析post请求结果

private:
    Ui::login_registerClass ui;
};

login_register.cpp

#pragma execution_character_set("utf-8")

#include "login_register.h"

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QMessageBox>
#include <QTimer>


login_register::login_register(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);
}

void login_register::on_btnLogin_clicked()
{
    //test_http_post();
    test_timeout_http_post();
}

void login_register::test_http_post()
{
    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();

    // 设置url
    QString url = "http://127.0.0.1:8080/login";

    // 设置头信息
    QNetworkRequest requestInfo;
    requestInfo.setUrl(QUrl(url));
    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));

    // setRawData
    QJsonObject rawJson;
    rawJson.insert("username", "zhangsan");
    rawJson.insert("password", "123456");

    QByteArray byte_array = QJsonDocument(rawJson).toJson();
  
    // 发送post请求
    QNetworkReply* reply = pHttpMgr->post(requestInfo, byte_array);

    if (reply)
    {
        // 添加事件循环机制,返回后再运行后面的
        connect(pHttpMgr, &QNetworkAccessManager::finished, 
            this, &login_register::post_requestFinished);
    }
}

void login_register::post_requestFinished(QNetworkReply* reply) 
{
    // 获取http状态码
    QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
    if (statusCode.isValid())
        qDebug() << "status code=" << statusCode.toInt();

    QVariant reason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
    if (reason.isValid())
        qDebug() << "reason=" << reason.toString();

    QNetworkReply::NetworkError err = reply->error();

    if (err != QNetworkReply::NoError) 
    {
        // 请求失败
        QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

        QMessageBox::information(this, "warn",
            "http post failed, error code = " + statusCode.toString() + " error info: " + reply->errorString());

        return;
    }
    else 
    {
        // 请求成功
        // 接收请求结果
        QByteArray responseByte = reply->readAll();
        QString strRes = responseByte;

        QMessageBox::information(this, "http post success",
            "post response = " + strRes);
    }
}

void login_register::test_timeout_http_post()
{
    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();

    // 设置url
    QString url = "http://127.0.0.1:8080/login";

    // 设置头信息
    QNetworkRequest requestInfo;
    requestInfo.setUrl(QUrl(url));
    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));

    // setRawData
    QJsonObject rawJson;
    rawJson.insert("username", "zhangsan");
    rawJson.insert("password", "123456");

    QByteArray byte_array = QJsonDocument(rawJson).toJson();

    // 发送post请求
    QNetworkReply* reply = pHttpMgr->post(requestInfo, byte_array);

    // 添加超时处理,1ms超时
    QEventLoop eventloop;
    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));

    // 比如设置1ms内完成请求,否则就认为是超时
    //如果1秒内没有收到 finished() 信号,定时器会触发并调用 eventloop 的 quit() 槽函数,退出事件循环
    QTimer::singleShot(1, &eventloop, &QEventLoop::quit);
    eventloop.exec();	//启动事件循环

    QByteArray array;
    if (reply->isFinished())    //当请求完成时,返回true
    {
        if (reply->error() == QNetworkReply::NoError)
        {
            //正常结束,读取响应数据
            QByteArray result = reply->readAll();
            QString strRes = result;

            QMessageBox::information(this, "http post success",
                "post response = " + strRes);
        }
        else
        {
            // 异常结束
            // 请求失败
            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

            QMessageBox::information(this, "warn",
                "http post failed, error code = " + statusCode.toString() + " error info: " + reply->errorString());

            return;
        }
    }
    else
    {
        // 请求超时
        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
        reply->abort();
        
        QMessageBox::information(this, "http post timeout", "http post timeout");
    }

    reply->deleteLater();   //表示请求事件循环后删除 reply 对象
}

main.cpp

#include "login_register.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    login_register w;
    w.show();
    return a.exec();
}

运行结果

image-20240707164555683

五、C++ Qt json解析技术

  Qt有json解析相关的库,不推荐使用,建议使用nlohmann/json,这个json适用于任何C++框架。nlohmann/json只需要一个头文件json.h,不需要编译成lib,直接放到项目中即可使用。

github地址:https://github.com/nlohmann/json

C++其他json库:

  • Jsoncpp

  • boost json

  • Qt Json(不推荐使用)

    示例:

    qtjson.h

    #pragma once
    
    #include <QtWidgets/QWidget>
    
    class qtjson : public QWidget
    {
        Q_OBJECT
    
    public:
        qtjson(QWidget *parent = nullptr);
        ~qtjson();
    
    };
    

    qtjson.cpp

    #include "qtjson.h"
    #include <string>
    #include <QJsonDocument> 
    #include <QJsonObject>
    #include <QDebug>
    
    using namespace std;
    
    qtjson::qtjson(QWidget *parent)
        : QWidget(parent)
    {
    
        string json_str = R"(
            {
                "date": "20220701",
                "level": 1,
                "msg": "register account",
                "status": "success",
                "username": "jackli"
            }
        )";
    
        //string json_str = R"({})";
    
        QString qstr = QString::fromStdString(json_str);
    
        QByteArray jbyte = qstr.toUtf8();  //QString转QByteArray
    
        QJsonParseError error;
        QJsonDocument jdoc = QJsonDocument::fromJson(jbyte, &error);
    
        if (error.error != QJsonParseError::NoError)
        {
            // 有错误
            qDebug() << "json parse error";
            return;
        }
        qDebug() << "json parse success";
    
        if (jdoc.isNull() || jdoc.isEmpty())
        {
            qDebug() << "json docment is empty";
            return;
        }
    
        QJsonObject jobj = jdoc.object();
        QString date = jobj["date"].toString();
        qDebug() << "date" << date;
    
        int level = jobj["level"].toInt();
        qDebug() << "level" << level;
    }
    
    qtjson::~qtjson()
    {}
    

    main.cpp

    #include "qtjson.h"
    #include <QtWidgets/QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        qtjson w;
        w.show();
        return a.exec();
    }
    

    输出结果

    json parse success
    date "20220701"
    level 1
    
  • nlohmann/json(推荐使用):序列化使用dump函数,反序列化使用parse函数

    // ch6.5_moderncppjson.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include <iostream>
    #include "json.hpp"
    #include <string>
    
    using json_t = nlohmann::json;
    
    using namespace std;
    
    int parse_array()
    {
        string jsonstr = R"(
            {
    	        "id":"2022",
    	        "name":{"EnglishName":"JackMa"},
    	        "title":[
    		        {
    			        "company":"taobao", 
    			        "position" : "AAA", 
    			        "salary" : "10000"
    		        },
    		        { 
    			        "company":"zhifubao",
    			        "position" : "CCC",
    			        "salary" : "890898" 
    		        },
    		        { 
    			        "company":"pingtouge",
    			        "position" : "KKK",
    			        "salary" : "76843" 
    		        }
    	        ]
            }
        )";
    
        try
        {
            json_t jsonObj = json_t::parse(jsonstr);
    
            //"name":{"EnglishName":"JackMa"},
            json_t nameJson = jsonObj["name"];
            string engName = nameJson["EnglishName"];
            cout << "engName = " << engName << endl;
    
            json_t titleJson = jsonObj["title"];
            int size = titleJson.size();
            cout << "size = " << size << endl;
    
            //for (int i = 0; i < size; i++)
            //{
            //    /*
            //    {
    			     //   "company":"taobao", 
    			     //   "position" : "AAA", 
    			     //   "salary" : "10000"
    		      //  },
            //    */
            //    cout << titleJson[i]["company"] << endl;
            //    cout << titleJson[i]["position"] << endl;
            //    cout << titleJson[i]["salary"] << endl;
            //}
    
            for (auto jsonItem : titleJson)
            {
                cout << jsonItem["company"] << endl;
                cout << jsonItem["position"] << endl;
                cout << jsonItem["salary"] << endl;
            }
        }
        catch (const std::exception&)
        {
            return -1;
        }
    
        return 0;
    }
    
    int main()
    {
        json_t jRawdata;
        jRawdata["username"] = "jackli";
        jRawdata["pwd"] = "123456";
    
        // 序列化,把json数据转成string
        string _rawdata = jRawdata.dump();
        cout << _rawdata << endl;
    
        // 反序列化
        string json_str = R"(
            {
                "date": "20220701",
                "level": 1,
                "msg": "register account",
                "status": "success",
                "username": "jackli"
            }
        )";
    
        try 
        {
            json_t j2 = json_t::parse(json_str);
            string date = j2["date"];
            cout << date << endl;
        }
        catch (std::exception& e)
        {
            cout << "json parse failed" << endl;
            return -1;
        }
    
        cout << endl;
        cout << "解析json数组" << endl;
        if (parse_array() != 0)
        {
            cout << "parse array failed" << endl;
        }
    
        return 0;
    }
    

六、libcurl源码编译

  下载libcurl源码,下载地址:https://curl.se/libcurl/,点击“Download page”,点击需要的版本下载

image-20240707203735964

  解压下载的文件

image-20240707204333326

  新建两个文件夹:build_vs2019_x86libcurl_sdk_x86

  • build_vs2019_x86:存放解决方案等
  • libcurl_sdk_x86:存放头文件、静态库与动态库

image-20240707204552427

  使用cmake进行编译,配置如下:

image-20240707204906317

  点击“Finish“并选择为”CMAKE_INSTALL_PREFIX"选择libcurl_sdk_x86

image-20240707205420155

  点击“Generate”即完成编译。

image-20240707205650627

  点击“Open Project”打开项目。

image-20240707210017219

  点击“生成解决方案”

image-20240707210224456

  右键“INSTALL”–>“仅用于项目”–>“仅生成”。生成如下:

image-20240707210428825

开发时,将这个目录放到项目中,附加头文件与lib即可使用libcurl。

七、C++ Qt使用libcurl进行http请求

  以下是使用 libcurl 进行 HTTP 请求的基本步骤:

  • 初始化:首先,你需要初始化 libcurl 会话。

    CURL *curl = curl_easy_init();
    if (curl) {
        // 进行操作
    }
    
  • 设置URL:使用 curl_easy_setopt 设置请求的 URL。

    curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
    
  • 设置回调函数:为响应数据设置回调函数,以便处理服务器的响应。

    libcurl获取请求的结果

    //回调函数:用于将响应结果写到数据流中
    size_t write_callback(void* ptr, size_t size, size_t nmemb, void* stream)
    {
        string data((const char*)ptr, (size_t)size * nmemb);
        *((stringstream*)stream) << data << endl;
        return size * nmemb;
    }
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &jsonRespon);  //    stringstream jsonRespon;
    
  • 设置回调数据:如果回调函数需要访问某些数据,可以通过 CURLOPT_WRITEDATA 设置。

    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &some_data);
    
  • 设置其他选项(可选):根据需要设置其他选项,例如请求头、超时时间、HTTP 版本等。

    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 设置请求头
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);       // 设置超时时间
    curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); // 设置HTTP版本
    
  • 执行请求:调用 curl_easy_perform 执行 HTTP 请求。

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
    		//
        ));
    }
    
  • 清理:完成请求后,清理 libcurl 会话。

    curl_easy_cleanup(curl);
    
  • 错误处理:检查 curl_easy_perform 的返回值,以确定请求是否成功,并适当处理错误。

示例:

#include <iostream>
#include "curl/curl.h"
#include <iostream>
#include <sstream>
#include <string>
#include "json.hpp"

using namespace std;

using json_t = nlohmann::json;

// 数据处理函数
size_t write_data(void* ptr, size_t size, size_t nmemb, void* stream)
{
    string data((const char*)ptr, (size_t)size * nmemb);
    *((stringstream*)stream) << data << endl;
    return size * nmemb;
}


int main()
{
    CURL* curl;
    CURLcode res;
    stringstream jsonRespon;

    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
        curl_easy_setopt(curl, CURLOPT_URL, "127.0.0.1:8080/login");
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        //curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        //const char* data = "{\r\n    \"username\":\"jackhui\",\r\n    \"password\":\"1234\"\r\n}";
        
        string data = R"(
            {
                "username":"jackhui",
                "password":"1234"
            }
        )";


        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
        //
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &jsonRespon);

        res = curl_easy_perform(curl);
        if (res != 0)
        {
            cout << "request failed" << endl;
            return -1;
        }
    }
    curl_easy_cleanup(curl);
    cout << "request success 222" << endl;

    string json = jsonRespon.str();

    try
    {
        json_t j = json_t::parse(json);
        cout << j.dump(4) << endl;

        string data = j["date"];
    }
    catch (std::exception& e)
    {
        cout << "json parse failed" << endl;
        return -2;
    }

    return 0;
}

八、websocket协议介绍

  websocket是HTML5中新增的一个协议,这个协议的出现,让客户端和服务器之间的数据交互变成全双工的。websocket的出现,最主要的变化是允许服务器主动给客户端推送数据。这一大改变,让websocket具有了以往其他协议无法比拟的实时通信能力。要实现websocket服务,需要服务端与客户端都得支持websocket协议才可以。目前看来,并没有太多标准性的框架来完成websocket服务。websocket协议可用于聊天、消息推送、多人在线业务

websocket协议详细链接:https://datatracker.ietf.org/doc/rfc6455/

websocket与http的异同

相同点:

  • 都是基于TCP,都是可靠性传输协议
  • 都是应用层协议

不同点:

  • websocket是持久连接,http是短连接;
  • websocket的协议是以ws/wss开头,http对应的是http/https
  • websocket是有状态的,http是无状态的
  • websocket连接之后服务器和客户端可以双向发送数据,http只能是客户端发起一次请求后,服务器才能返回数据
  • websocket连接建立之后,不需要再发送request请求,数据直接从TCP通道传输

C++ websocket的实现

  websocket不同的语言都会有各自的实现,并且每种语言里都有多个实现。websocketpp是用C++实现的一个websocket库,用来支持websocket协议。如果是C++程序员,建议使用websocketpp来做开发。

github地址:https://github.com/zaphoyd/websocketpp

当然,Qt中也有websocket的实现,分别是QWebSocketServer与QWebSocket。

  • QWebSocketServer
    • QWebSocketServer 类是一个非阻塞的 WebSocket 服务器,用于监听和接受来自客户端的 WebSocket 连接请求。
    • 服务器可以设置监听的端口和地址,并且可以配置为使用安全的 WebSocket(wss://)或非安全的 WebSocket(ws://)。
    • 当有新的客户端尝试连接时,QWebSocketServer 会发出 newConnection 信号,应用程序可以连接到这个信号来处理新的连接请求。
    • 服务器还提供了方法来关闭连接和停止监听。
  • QWebSocket
    • QWebSocket 类代表一个 WebSocket 连接,可以是客户端也可以是服务器端的 WebSocket。
    • 它提供了发送和接收消息的方法,包括文本消息和二进制消息。
    • QWebSocket 可以连接到不同的信号,如 textMessageReceived 用于接收文本消息,binaryMessageReceived 用于接收二进制消息,以及 disconnected 用于处理客户端断开连接的情况。
    • 这个类还提供了状态管理,例如检查连接是否打开、关闭或正在关闭等。

在 Qt 中实现 WebSocket 通信的基本流程是:

  • 创建和配置 QWebSocketServer 对象,设置监听的端口和地址。
  • 连接 QWebSocketServernewConnection 信号到一个槽函数,以处理新的连接请求。
  • 在槽函数中,使用 QWebSocketServer::nextPendingConnection 获取新的连接,并将其转换为 QWebSocket 对象。
  • 使用 QWebSocket 对象来管理与客户端的通信,包括发送和接收消息,以及处理连接的断开。

九、Qt实现websocket server

  Qt 提供了一套完整的网络编程支持,包括对 WebSocket 的支持。WebSocket 是一种网络通信协议,允许开放一个持久的连接来实现服务器和客户端之间的双向通信。以下是使用 Qt 实现 WebSocket 服务器的基本步骤:

  • 创建 Qt 项目:首先,你需要创建一个 Qt 应用程序项目。

  • 添加 WebSocket 模块:确保你的项目文件(.pro)中包含了 WebSocket 模块,例如:

    QT += websockets
    
  • 包含必要的头文件:在你的源代码文件中,包含 WebSocket 相关的头文件:

    #include <QtWebSocketServer>
    #include <QWebSocket>
    
  • 创建 WebSocket 服务器:创建一个 QtWebSocketServer 对象

    m_WebSocketServer = new QWebSocketServer(u8"server", QWebSocketServer::NonSecureMode);
    
  • 设置服务器监听的端口和地址

    if (m_WebSocketServer->listen(QHostAddress(ip), port.toInt()))
    {
        ui.textEdit_RecvMsg->append(u8"服务开启成功");
        ui.btnOpenServer->setEnabled(false);
        //监听成功,看是否有新的client连接
        connect(m_WebSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
    }
    else
    {
        QMessageBox::information(this, u8"提示", u8"监听失败, 是否开启了代理,或者IP错误");
    }
    
  • 处理新的连接:当新的 WebSocket 连接建立时,这个函数会被触发

    void WebsocketServerDemo::onNewConnection()
    {
        pSocket = m_WebSocketServer->nextPendingConnection(); //从 WebSocket 服务器获取等待中的连接
        m_clients << pSocket;
    	//当 pSocket 接收到文本消息时,processTextMessage 槽函数会被调用。
        connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString)));
        //当客户端断开连接时调用 socketDisconnected 槽函数;客户端掉线处理
        connect(pSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
    
        QString peerName = pSocket->requestUrl().toString(); //获取连接的客户端的请求 URL,并将其转换为 QString 类型
        cout << "peerName = " << peerName.toStdString() << endl;
    
        //将ip和socket保存到map
        mapSocket[peerName] = pSocket;
    
        ui.listWidget_OnlineUser->addItem(peerName);
    }
    
  • 接收和发送消息

    使用 processTextMessage 函数来处理接收消息,

    //处理接收到的消息
    void WebsocketServerDemo::processTextMessage(QString message) 
    {
        QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
        QString item = pSocket->requestUrl().toString();	//知道是哪个client发过来的消息
        ui.textEdit_RecvMsg->append(time + "" + item + "\n" + message);
    
        //处理消息转发
        //...
    
    }
    

    使用 QWebSocket::sendTextMessage 来发送消息:

    void WebsocketServerDemo::on_btnSend_clicked()
    {
        QString msg = ui.textEdit_send->document()->toPlainText();
    
        for (auto sk : m_clients)
        {
            //这里类似于群发,server向所有client发送消息
            sk->sendTextMessage(msg);
        }
    }
    
  • 关闭连接:客户端掉线处理

    //客户端连接断开的操作
    void WebsocketServerDemo::socketDisconnected() 
    {
        for (auto sk : m_clients)
        {
            if (!sk->isValid())
            {
                QString temp_key;
                ui.textEdit_RecvMsg->append("map size = " + QString(mapSocket.size()) + "\n");
                for (auto it = mapSocket.begin(); it!=mapSocket.end(); it++)
                {
                    if (it.value() == sk)
                    {
                        //删除项
                        QList<QListWidgetItem*> list;
                        list = ui.listWidget_OnlineUser-> findItems(it.key(), Qt::MatchCaseSensitive);
    
                        QListWidgetItem* sel = list[0];
                        int r = ui.listWidget_OnlineUser->row(sel);
    
                        QListWidgetItem* item = ui.listWidget_OnlineUser->takeItem(r);
                        ui.listWidget_OnlineUser->removeItemWidget(item);
    
                        delete item;
                        m_clients.removeOne(sk);
    
                        QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
                        ui.textEdit_RecvMsg->append(time + "" + it.key() + "下线了\n");
    
                        temp_key = it.key();
                    }
                }
    
                mapSocket.remove(temp_key);
                ui.textEdit_RecvMsg->append("after remove, map size = " + QString(mapSocket.size()) + "\n");
            }
        }
    }
    
  • 关闭 websocket server

    void WebsocketServerDemo::on_btnCloseServer_clicked()
    {
        if (m_WebSocketServer)
        {
            if (m_WebSocketServer->isListening())
            {
                m_WebSocketServer->close();
                ui.btnOpenServer->setEnabled(true);
                ui.textEdit_RecvMsg->append(u8"服务关闭");
            }
        }
    
    
  • 运行和测试:编译并运行应用程序,然后使用 WebSocket 客户端工具来测试服务器。

示例:

WebSocketServer.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_WebsocketServer.h"
#include <QWebSocketServer>
#include <QWebSocket>
#include <QMap>

class WebsocketServerDemo : public QWidget
{
    Q_OBJECT

public:
    WebsocketServerDemo(QWidget *parent = Q_NULLPTR);
    ~WebsocketServerDemo();

private slots:
    void on_btnOpenServer_clicked();    //开启服务按钮槽函数
    void on_btnCloseServer_clicked();   //关闭服务按钮槽函数
    void on_btnSend_clicked();          //发送按钮槽函数

    void onNewConnection();             //处理新的连接
    void processTextMessage(QString message);   //处理接收到的文本消息
    void socketDisconnected();          //处理客户端断开连接的情况

private:
    Ui::WebsocketServerClass ui;

    QWebSocketServer* m_WebSocketServer = nullptr;

    QList<QWebSocket*> m_clients;
    bool m_debug;
    QWebSocket* pSocket;
    QDateTime* current_date_time;
    QMap<QString, QWebSocket*> mapSocket;
};

WebSocketServer.cpp

#include "WebsocketServer.h"
#include <QMessageBox>
#include <string>
#include <Windows.h>
#include <iostream>

using namespace std;

WebsocketServerDemo::WebsocketServerDemo(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
   
    this->setWindowTitle(u8"Websocket Server");
    this->resize(1000, 600);

    ui.lineEdit_IP->setText("192.168.0.109");
    ui.lineEdit_Port->setText("8000");

    m_WebSocketServer = new QWebSocketServer(u8"server", QWebSocketServer::NonSecureMode);
}

WebsocketServerDemo::~WebsocketServerDemo()
{
    if (m_WebSocketServer)
    {
        if(m_WebSocketServer->isListening())
            m_WebSocketServer->close();
    }  
}

void WebsocketServerDemo::on_btnOpenServer_clicked()
{
    QString ip = ui.lineEdit_IP->text();
    QString port = ui.lineEdit_Port->text();

    if (m_WebSocketServer->listen(QHostAddress(ip), port.toInt()))
    {
        ui.textEdit_RecvMsg->append(u8"服务开启成功");
        ui.btnOpenServer->setEnabled(false);
        //监听成功,看是否有新的client连接
        connect(m_WebSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
    }
    else
    {
        QMessageBox::information(this, u8"提示", u8"监听失败, 是否开启了代理,或者IP错误");
    }
}

void WebsocketServerDemo::on_btnCloseServer_clicked()
{
    if (m_WebSocketServer)
    {
        if (m_WebSocketServer->isListening())
        {
            m_WebSocketServer->close();
            ui.btnOpenServer->setEnabled(true);
            ui.textEdit_RecvMsg->append(u8"服务关闭");
        }
    }
}

void WebsocketServerDemo::onNewConnection()
{
    pSocket = m_WebSocketServer->nextPendingConnection(); //从 WebSocket 服务器获取等待中的连接
    m_clients << pSocket;
    //当 pSocket 接收到文本消息时,processTextMessage 槽函数会被调用。
    connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString)));
    //当客户端断开连接时调用 socketDisconnected 槽函数
    connect(pSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));

    QString peerName = pSocket->requestUrl().toString(); //获取连接的客户端的请求 URL,并将其转换为 QString 类型
    cout << "peerName = " << peerName.toStdString() << endl;

    //将ip和socket保存到map
    mapSocket[peerName] = pSocket;

    ui.listWidget_OnlineUser->addItem(peerName);
}

//处理接收到的消息
void WebsocketServerDemo::processTextMessage(QString message) 
{
    QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
    QString item = pSocket->requestUrl().toString();
    ui.textEdit_RecvMsg->append(time + "" + item + "\n" + message);

    //处理消息转发
    //...

}

//客户端连接断开的操作
void WebsocketServerDemo::socketDisconnected() 
{
    for (auto sk : m_clients)
    {
        if (!sk->isValid())
        {
            QString temp_key;
            ui.textEdit_RecvMsg->append("map size = " + QString(mapSocket.size()) + "\n");
            for (auto it = mapSocket.begin(); it!=mapSocket.end(); it++)
            {
                if (it.value() == sk)
                {
                    //删除项
                    QList<QListWidgetItem*> list;
                    list = ui.listWidget_OnlineUser-> findItems(it.key(), Qt::MatchCaseSensitive);

                    QListWidgetItem* sel = list[0];
                    int r = ui.listWidget_OnlineUser->row(sel);

                    QListWidgetItem* item = ui.listWidget_OnlineUser->takeItem(r);
                    ui.listWidget_OnlineUser->removeItemWidget(item);

                    delete item;
                    m_clients.removeOne(sk);

                    QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
                    ui.textEdit_RecvMsg->append(time + "" + it.key() + "下线了\n");

                    temp_key = it.key();
                }
            }

            mapSocket.remove(temp_key);
            ui.textEdit_RecvMsg->append("after remove, map size = " + QString(mapSocket.size()) + "\n");
        }
    }
}

void WebsocketServerDemo::on_btnSend_clicked()
{
    QString msg = ui.textEdit_send->document()->toPlainText();

    for (auto sk : m_clients)
    {
        sk->sendTextMessage(msg);
    }
}

main.cpp

#include "WebsocketServer.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    WebsocketServerDemo w;
    w.show();
    return a.exec();
}

运行结果

image-20240708121101079

十、Qt实现websocket client

  在Qt中实现WebSocket客户端的基本步骤如下:

  • 创建Qt项目:首先,创建一个Qt应用程序项目。

  • 添加WebSocket模块:确保你的项目文件(.pro)中包含了WebSocket模块:

    QT += websockets
    
  • 包含必要的头文件:在源代码文件中,包含WebSocket相关的头文件:

    #include <QtWebSockets/QWebSocket>
    
  • 创建QWebSocket对象:在应用程序中创建一个 QWebSocket 对象,这个对象将用于管理WebSocket连接:

    QWebSocket socket;
    
  • 设置连接服务器:设置WebSocket客户端要连接的URL,这个URL应该是WebSocket服务器的地址:

    QString _text = ui.lineEdit_URL->text();
    QUrl url = QUrl(_text);
    m_websocket.open(url);
    
  • 连接信号和槽:连接 QWebSocket 对象的信号到相应的槽函数,以便处理不同的事件,如连接打开、接收到消息、连接关闭等:

    connect(&m_websocket, SIGNAL(connected()), this, SLOT(onconnected()));
    connect(&m_websocket, SIGNAL(disconnected()), this, SLOT(closeConnection()));
    connect(&m_websocket, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString)));
    
  • 发送消息:当连接建立后,可以使用 QWebSocket::sendTextMessage 方法发送文本消息:

    //发送消息
    void WebSocketClientDemo::on_btnSend_clicked()
    {
        QString msg = ui.textEdit_send->document()->toPlainText();
    
        string dataMsg = R"(
            "sender":"10002",
            "receiver":"10001",
            "msg":"你好"
        )";
        
        m_websocket.sendTextMessage(msg);
    }
    
  • 处理接收到的消息:处理从服务器接收到的消息:

    //处理接收到的消息
    void WebSocketClientDemo::onTextMessageReceived(const QString& message)
    {
        QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
        ui.textEdit_recv->setText(time + "\n" + message);
    }
    
  • 处理连接和断开事件:实现槽函数来处理连接建立和断开的事件:

    void WebSocketClientDemo::onconnected() 
    {
        ui.label_ConnectStatus->setText(tr("connected"));
        ui.btnConnect->setEnabled(false);
        ui.btnDisconnect->setEnabled(true);
    }
    
    void WebSocketClientDemo::on_btnDisconnect_clicked()
    {
        m_websocket.close();
    }
    
  • 运行和测试:编译并运行你的应用程序,然后使用WebSocket服务器来测试客户端的功能。

示例:

WebSocketClientDemo.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_WebSocketClientDemo.h"
#include <QLineEdit>
#include <QLabel>
#include <QTextEdit>
#include <QListWidget>
#include <QPushButton>
#include <QSpinBox>
#include <QButtonGroup>
#include <QObject>
#include <QWidget>
#include <QUrl>
#include <time.h>
#include <QByteArray>
#include <QWebSocket>

class WebSocketClientDemo : public QWidget
{
    Q_OBJECT

public:
    WebSocketClientDemo(QWidget *parent = Q_NULLPTR);
    ~WebSocketClientDemo();

private slots:
    void on_btnConnect_clicked();       //连接按钮槽函数
    void on_btnDisconnect_clicked();    //断开按钮槽函数
    void on_btnSend_clicked();          //发送按钮槽函数

    void onconnected();                 //处理连接后的槽函数
    void onTextMessageReceived(const QString& message); //处理接收到的消息的槽函数
    void closeConnection();             //处理断开连接的槽函数

private:
    Ui::WebSocketClientDemoClass ui;

    QUrl m_url;
    QWebSocket m_websocket;
    bool m_debug;
    QDateTime* current_date_time;
};

WebSocketClientDemo.cpp

#include "WebSocketClientDemo.h"
#include <QLabel>
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QtCore>
#include <QDebug>
#include <iostream>
#include <string>

using namespace std;

WebSocketClientDemo::WebSocketClientDemo(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);

    ui.lineEdit_URL->setText("ws://192.168.0.109:8000/topic=10001");
    ui.label_ConnectStatus->clear();

    connect(&m_websocket, SIGNAL(connected()), this, SLOT(onconnected()));
    connect(&m_websocket, SIGNAL(disconnected()), this, SLOT(closeConnection()));
    connect(&m_websocket, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString)));
}

WebSocketClientDemo::~WebSocketClientDemo()
{
    m_websocket.errorString();
    m_websocket.close();
}

//断开连接操作
void WebSocketClientDemo::closeConnection() 
{   
    ui.label_ConnectStatus->setText("disconnected");
}

//连接服务器
void WebSocketClientDemo::on_btnConnect_clicked()
{
    QString _text = ui.lineEdit_URL->text();
    QUrl url = QUrl(_text);
    m_websocket.open(url);
}

//连接上之后
void WebSocketClientDemo::onconnected() 
{
    ui.label_ConnectStatus->setText(tr("connected"));
    ui.btnConnect->setEnabled(false);
    ui.btnDisconnect->setEnabled(true);
}

//收到消息
void WebSocketClientDemo::onTextMessageReceived(const QString& message)
{
    QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
    ui.textEdit_recv->setText(time + "\n" + message);
}

//断开
void WebSocketClientDemo::on_btnDisconnect_clicked()
{
    m_websocket.close();
}

//发送消息
void WebSocketClientDemo::on_btnSend_clicked()
{
    QString msg = ui.textEdit_send->document()->toPlainText();

    string dataMsg = R"(
        "sender":"10002",
        "receiver":"10001",
        "msg":"你好"
    )";
    
    m_websocket.sendTextMessage(msg);
}

main.cpp

#include "WebSocketClientDemo.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    WebSocketClientDemo w;
    w.show();
    return a.exec();
}

运行结果

image-20240708122529571

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值