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) | 方法 | 绑定指定的地址和端口号,并开始监听。 | bind 和 listen |
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();});
}
效果: