文章目录
一、http简介
参考:HTTP教程
HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP协议是用于从万维网服务器传输超文本到本地浏览器的传送协议。HTTP 是万维网(WWW)的数据通信的基础,设计目的是确保客户端与服务器之间的通信,是互联网上最常用的协议之一。HTTP 是一个基于 TCP/IP 通信协议来传递数据的(HTML 文件、图片文件、查询结果等)。
HTTP的请求与响应:
HTTP 的基本工作原理是客户端(通常是 web 浏览器)向服务器发送请求,服务器接收到请求后,返回相应的资源。这些资源可以是网页、图像、音频文件、视频等。HTTP 使用了客户端-服务器模型,其中客户端发送请求,服务器返回响应。
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框架有:
drogon
oat++
二、后端API测试工具postman使用介绍
Postman 是一个流行的 API(应用程序编程接口)开发工具,它提供了一个用户友好的界面来测试、开发和调试 API。接口一般都使用这个工具测试。
postman安装:
双击后即可安装,安装完成后,首次打开的界面需要注册,直接关闭,再打开就不用注册了。
Postman请求后端API,例如:用户登陆127.0.0.1:8080/login,设置rawdata json,发送post请求,返回json数据。
Postman生成客户端请求代码,
请求百度
三、Qt http相关类
1.QNetworkAccessManager
QNetworkAccessManager 类允许应用程序发送网络请求并接收响应。
Header:
#include <QNetworkAccessManager>
qmake:
QT += network
详细内容可参考: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();
}
运行结果:
五、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”,点击需要的版本下载
解压下载的文件
新建两个文件夹:build_vs2019_x86
和libcurl_sdk_x86
- build_vs2019_x86:存放解决方案等
- libcurl_sdk_x86:存放头文件、静态库与动态库
使用cmake进行编译,配置如下:
点击“Finish“并选择为”CMAKE_INSTALL_PREFIX"选择libcurl_sdk_x86
点击“Generate”即完成编译。
点击“Open Project”打开项目。
点击“生成解决方案”
右键“INSTALL”–>“仅用于项目”–>“仅生成”。生成如下:
开发时,将这个目录放到项目中,附加头文件与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来做开发。
当然,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
对象,设置监听的端口和地址。 - 连接
QWebSocketServer
的newConnection
信号到一个槽函数,以处理新的连接请求。 - 在槽函数中,使用
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();
}
运行结果:
十、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();
}
运行结果: