1. http 基础
- HTTP 基础教程
- C++ Web 框架
- drogon
- oatpp
2. C++ Qt 用户登录、注册功能实现
- 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();
void test_timeout_http_post();
private slots:
void on_btnLogin_clicked();
void post_requestFinished(QNetworkReply* reply);
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 内完成请求,否则就认为是超时
QTimer::singleShot(1000, &eventloop, &QEventLoop::quit);
eventloop.exec();
QByteArray array;
if (reply->isFinished()) {
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();
}
3. Qt json 解析介绍
- ch65_qtjson.cpp
#include "ch65_qtjson.h"
#include <string>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
using namespace std;
ch65_qtjson::ch65_qtjson(QWidget *parent) : QWidget(parent) {
ui.setupUi(this);
/*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);
if (qstr.isEmpty()) {
qDebug() << "qstr is empty";
return;
}
QByteArray jbyte = qstr.toLocal8Bit();
// 捕获转换过程中出现的错误
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;
}
ch65_qtjson::~ch65_qtjson() {}
4. C++ nlohmann::json 使用介绍
-
nlohmann::json
-
Qt 有 json 解析相关的类,但不推荐使用,建议使用 nlohmann::json,这个 json 适用于任何 C++ 框架
-
nlohmann::json 只需要一个头文件 json.hpp,不需要编译成 lib,直接放到项目中即可使用
-
main.cpp
#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
json_t nameJson = jsonObj["name"];
string engName = nameJson["EnglishName"];
cout << "engName = " << engName << endl;
// 取出 title
json_t titleJson = jsonObj["title"];
int size = titleJson.size();
cout << "size = " << size << 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 数据转成 string */
json_t jRawdata;
jRawdata["username"] = "jackli";
jRawdata["pwd"] = "123456";
// nlohmann::json 中的 dump 方法实现数据转储
string _rawdata = jRawdata.dump(4);
cout << _rawdata << endl;
/* 反序列化,把 string 数据转成 json */
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;
}
/* 解析复杂 json 数据 */
cout << "解析 json 数组" << endl;
if (parse_array() != 0) {
cout << "parse array failed" << endl;
}
return 0;
}
5. libcurl 解析
- libcurl 是一个跨平台的网络协议库,支持 http、https,ftp,telnet 等应用层协议
- libcurl 主要功能就是用不同的协议连接和沟通不同的服务器
5.1 libcurl 编译
- 流程同 CEF 编译
- 参考链接
5.2 使用 libcurl 进行 http post 请求
#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() {
/* 以下代码为通过 Postman 生成 */
// Postman 是一款用于发送 HTTP 请求的测试工具
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);
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;
/* 以上代码为通过 Postman 生成 */
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;
}
6. websocket
6.1 基本概念
-
websocket 是 HTML5 中新增的一个协议,这个协议的出现,让客户端和服务器之前的数据交互变成全双工的
-
websocket 的出现,最主要的变化是允许服务器主动给客户端推送数据。这一大改变,就让 websocket 具有了以往其它协议无法比拟的实时通信能力。要实现 websocket 服务,需要客户端和服务端都得支持 websocket 协议才可以
-
websocket 详解
-
Websocket 能做什么?
- 聊天、消息推送、多人在线业务
- 推荐一个开源项目 OpenIM
-
websocket 与 http 的区别
- 相同点
- 都是一样基于 TCP 的,都是可靠性传输协议
- 都是应用层协议
- 不同点
- websocket 是持久连接,http 是短连接
- websocket 的协议是以 ws/wss 开头,http 对应的是 http/https
- websocket 是有状态的,http 是无状态的
- websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据
- websocket 连接建立之后,不需要再发送 request 请求,数据直接从 TCP 通道传输
- 相同点
-
websocketpp 是用 c++ 实现的一个 websocket 库,用来支持 websocket 协议
- websocketpp
6.2 C++ Qt 实现 websocket server
Qt websocket 的实现(需包含 websockets 第三方模块)
- QWebSocketServer(服务端)
- QWebSocket(客户端)
- 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.74.1");
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();
// 监听 ip 和端口
if (m_WebSocketServer->listen(QHostAddress(ip), port.toInt())) {
ui.textEdit_RecvMsg->append(u8"服务开启成功");
ui.btnOpenServer->setEnabled(false);
// 服务成功开启则触发 onNewConnection() 槽函数
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();
m_clients << pSocket;
// 处理接到消息的信号
connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString)));
// 处理断线的信号
connect(pSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
QString peerName = pSocket->requestUrl().toString();
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);
}
}
6.3 C++ Qt 实现 websocket client
- 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.74.1: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);
}