当前位置: 首页 > news >正文

Qt——网络通信(UDP/TCP/HTTP)

要使用Qt的网络通信,要先引入Network模块

需要在CMake中添加如下语句:

find_package(Qt6 COMPONENTS Network REQUIRED)
target_link_libraries(your_target_name PRIVATE Qt6::Network)

UDP

主要的类有两个:

QUdpSocket :表示一个UDP的socket文件

名称类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的端口号.bind
receiveDatagram()方法返回 QNetworkDatagram,读取一个 UDP 数据报.recvfrom
writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报.sendto
readyRead信号在收到数据并准备就绪后触发.无 (类似于 IO 多路复用的通知机制)

QNetworkDataGram :表示一个UDP的数据包

名称类型说明对标原生 API
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16)构造函数通过 QByteArray、目标 IP 地址、目标端口号构造一个 UDP 数据报,通常用于发送数据时
data()方法获取数据报内部持有的数据,返回 QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址无,recvfrom 包含了该功能
senderPort()方法获取数据报中包含的对端的端口号无,recvfrom 包含了该功能

利用上面的接口,实现一个带有图形化界面的Echo服务器和客户端:

服务器:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QUdpSocket>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void handlerRequest();private:bool process(const QString& request, QString* response);private:Ui::Widget *ui;QUdpSocket* socket_ = nullptr;
};
#endif  // WIDGET_H// widget.cpp
#include "widget.h"#include <QMessageBox>
#include <QNetworkDatagram>#include "./ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("服务器");ui->listWidget->setWindowTitle("服务器日志");socket_ = new QUdpSocket(this);  // 创建UDP套接字// connectconnect(socket_, &QUdpSocket::readyRead, this, &Widget::handlerRequest);// 进行端口绑定if (!socket_->bind(QHostAddress::Any, 8080)) {QMessageBox::critical(this, "服务器启动失败", socket_->errorString());return;}
}Widget::~Widget() { delete ui; }void Widget::handlerRequest() {const QNetworkDatagram data = socket_->receiveDatagram();QString request = data.data();QString response;if (!process(request, &response)) {//...}QNetworkDatagram send_data(response.toUtf8(), data.senderAddress(), data.senderPort());socket_->writeDatagram(send_data);QString log = "[" + data.senderAddress().toString() + ":" + QString::number(data.senderPort()) +"]: " + request;ui->listWidget->addItem(log);
}// 请求处理函数
bool Widget::process(const QString &request, QString *response) {*response = request;return true;
}

客户端:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QUdpSocket>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();void handlerResponse();private:Ui::Widget *ui;QUdpSocket *socket_ = nullptr;
};
#endif  // WIDGET_H// widget.cpp
#include "widget.h"#include <QNetworkDatagram>#include "./ui_widget.h"const QString SERVER_IP = "127.0.0.1";
const uint16_t SERVER_PORT = 8080;Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("客户端");ui->pushButton->setShortcut(Qt::Key_Return);socket_ = new QUdpSocket(this);  // 创建UDP套接字// connectconnect(socket_, &QUdpSocket::readyRead, this, &Widget::handlerResponse);
}Widget::~Widget() { delete ui; }void Widget::on_pushButton_clicked() {QString text = this->ui->lineEdit->text();this->ui->lineEdit->clear();const QNetworkDatagram send_data(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);socket_->writeDatagram(send_data);
}void Widget::handlerResponse() {const QNetworkDatagram& data = socket_->receiveDatagram();this->ui->listWidget->addItem("RECV: " + data.data());
}

效果:

在这里插入图片描述


TCP

核心的两个类:

QTcpServer,用于监听端口,获取客户端连接:

名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听。bindlisten
nextPendingConnection()方法从系统中获取到一个已经建立好的 TCP 连接。
返回一个 QTcpSocket,表示这个客户端的连接。
通过这个 socket 对象完成和客户端之间的通信。
accept
newConnection信号有新的客户端建立连接好之后触发。无(但类似于 IO 多路复用中的通知机制)

QTcpSocket,用于客户端和服务器的连接以及通信

名称类型说明对标原生 API
readAll()方法读取当前接收缓冲区中的所有数据,返回 QByteArray 对象read
write(const QByteArray&)方法把数据写入 socket 中write
deleteLater方法暂时把 socket 对象标记为无效,Qt 会在下个事件循环中析构释放该对象无(类似于 “半自动化的垃圾回收”)
connectToHost方法客户端连接到服务器,非阻塞connect
waitForConnected方法等待,直到连接建立,阻塞
readyRead信号有数据到达并准备就绪时触发无(类似于 IO 多路复用的通知机制)
disconnected信号连接断开时触发无(类似于 IO 多路复用的通知机制)

利用上面的接口,实现一个带有图形化界面的Echo服务器和客户端:

服务器:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QTcpServer>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private:void handlerConnection();bool process(const QString& request, QString* response);private:Ui::Widget *ui;QTcpServer* server_ = nullptr;
};
#endif  // WIDGET_H// widget.cpp
#include "widget.h"#include <QMessageBox>
#include <QTcpSocket>#include "./ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("服务器");// 创建TCP服务器server_ = new QTcpServer(this);// connectconnect(server_, &QTcpServer::newConnection, this, &Widget::handlerConnection);// 绑定 + 监听if (!server_->listen(QHostAddress::Any, 8080)) {QMessageBox::critical(this, "服务器启动错误", server_->errorString());exit(-1);}
}Widget::~Widget() { delete ui; }void Widget::handlerConnection() {// 获得与客户端通信的socketQTcpSocket *socket = server_->nextPendingConnection();const QString log = "[" + socket->peerAddress().toString() + ":" +QString::number(socket->peerPort()) + "] : " + "连接建立";this->ui->listWidget->addItem(log);// connect 消息处理connect(socket, &QTcpSocket::readyRead, this, [=]() {const QString &data = socket->readAll();  // 接收请求QString response;// 处理请求,构造响应if (!process(data, &response)) {//...}// 返回响应socket->write(response.toUtf8());const QString log = "[" + socket->peerAddress().toString() + ":" +QString::number(socket->peerPort()) + "] : " + "request: " + data +", response: " + response;this->ui->listWidget->addItem(log);});// connect 连接断开connect(socket, &QTcpSocket::disconnected, this, [=]() {const QString log = "[" + socket->peerAddress().toString() + ":" +QString::number(socket->peerPort()) + "] : " + "连接断开";this->ui->listWidget->addItem(log);// 下一个事件循环,自动释放 TcpSocketsocket->deleteLater();});
}bool Widget::process(const QString &request, QString *response) {*response = request;return true;
}

客户端:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QTcpSocket>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();private:Ui::Widget *ui;QTcpSocket *client_ = nullptr;
};
#endif  // WIDGET_H// widget.cpp
#include "widget.h"#include <QMessageBox>#include "./ui_widget.h"const QString SERVER_IP = "127.0.0.1";
const uint16_t SERVER_PORT = 8080;Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("客户端");ui->pushButton->setShortcut(Qt::Key_Return);ui->lineEdit->setPlaceholderText("请输入请求信息");// 创建客户端client_ = new QTcpSocket(this);// connect 消息处理connect(client_, &QTcpSocket::readyRead, this, [=]() {const QString& data = client_->readAll();this->ui->listWidget->addItem("RECV: " + data);});// 连接服务器,并等待建立成功client_->connectToHost(QHostAddress(SERVER_IP), SERVER_PORT);if (!client_->waitForConnected()) {QMessageBox::critical(this, "连接服务器失败", client_->errorString());exit(-1);}
}Widget::~Widget() { delete ui; }void Widget::on_pushButton_clicked() {const QString& send_data = ui->lineEdit->text();ui->lineEdit->clear();client_->write(send_data.toUtf8());
}

效果:

在这里插入图片描述


HTTP

关键类:

QNetworkAccessManage ,提供了HTTP的核心操作

方法说明
get(const QNetworkRequest& )发起一个 HTTP GET 请求,返回 QNetworkReply 对象
post(const QNetworkRequest& , const QByteArray& )发起一个 HTTP POST 请求,返回 QNetworkReply 对象

QNetworkRequest ,表示一个HTTP请求,不包含body

方法说明
QNetworkRequest(const QUrl& )通过 URL 构造一个 HTTP 请求
setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)设置请求头

QNetworkReply ,表示一个HTTP响应

方法说明
error()获取出错状态
errorString()获取出错原因的文本
readAll()读取响应 body
header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值
deleteLater暂时把对象标记为无效,Qt 会在下个事件循环中析构释放该对象

其还有一个信号finished,会在客户端收到完整的响应数据后触发

例如:

#ifndef WIDGET_H
#define WIDGET_H#include <QNetworkAccessManager>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();private:Ui::Widget *ui;QNetworkAccessManager *manager_ = nullptr;
};
#endif  // WIDGET_H// widget.cpp
#include "widget.h"#include <QNetworkReply>
#include <QNetworkRequest>#include "./ui_widget.h"Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowTitle("HTTP Client");ui->lineEdit->setPlaceholderText("请输入要获取的 URL");ui->pushButton->setShortcut(Qt::Key_Return);ui->plainTextEdit->setReadOnly(true);// 创建 http 管理器manager_ = new QNetworkAccessManager(this);
}Widget::~Widget() { delete ui; }void Widget::on_pushButton_clicked() {const QString& url = ui->lineEdit->text();ui->lineEdit->clear();// 构造请求QNetworkRequest request{QUrl(url)};// 发送get请求QNetworkReply* reply = manager_->get(request);// connect 相应获取处理方法connect(reply, &QNetworkReply::finished, this, [=]() {if (reply->error() == QNetworkReply::NoError) {QString data = reply->readAll();this->ui->plainTextEdit->setPlainText(data);} else {this->ui->plainTextEdit->setPlainText(reply->errorString());}// 获取到响应后,就需要释放资源reply->deleteLater();});
}

效果:

在这里插入图片描述

http://www.xdnf.cn/news/1356319.html

相关文章:

  • Linux学习-TCP网络协议
  • Linux shell脚本数值计算与条件执行
  • (计算机网络)JWT三部分及 Signature 作用
  • 如何在 IDEA 中在启动 Spring Boot 项目时加参数
  • [Windows] PDF-XChange Editor Plus官方便携版
  • 海盗王3.0客户端从32位升级64位之路
  • 操作系统文件系统
  • [e3nn] 等变神经网络 | 线性层o3.Linear | 非线性nn.Gate
  • Excel 转化成JSON
  • GPT 模型详解:从原理到应用
  • 第16届蓝桥杯C++中高级选拔赛(STEMA)2024年12月22日真题
  • 以国产IoTDB为代表的主流时序数据库架构与性能深度选型评测
  • 对象作为HashMap的key的注意事项
  • 30分钟通关二分查找:C语言实现+LeetCode真题
  • 机器学习算法-朴素贝叶斯
  • 优化OpenHarmony中lspci命令实现直接获取设备具体型号
  • 机械学习综合练习项目
  • 基于SpringBoot的新能源汽车租赁管理系统【2026最新】
  • Linux 系统管理核心概念与常用命令速查
  • 春秋云镜 Hospital
  • 【Qt开发】常用控件(六)
  • 一个简洁的 C++ 日志模块实现
  • 【数位DP】D. From 1 to Infinity
  • 金山办公的服务端开发工程师-25届春招笔试编程题
  • Python训练营打卡 DAY 45 Tensorboard使用介绍
  • 基于电磁频谱地图的辐射源定位算法复现
  • 基于TimeMixer现有脚本扩展的思路分析
  • 基础IO
  • CryptSIPVerifyIndirectData函数分析
  • 刷题日记0823