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

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

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

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服务器和客户端:

服务器:

cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QUdpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    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套接字

    // connect
    connect(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;
}

客户端:

cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QUdpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    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套接字

    // connect
    connect(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服务器和客户端:

服务器:

cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QTcpServer>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    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);

    // connect
    connect(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() {
    // 获得与客户端通信的socket
    QTcpSocket *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);

        // 下一个事件循环,自动释放 TcpSocket
        socket->deleteLater();
    });
}

bool Widget::process(const QString &request, QString *response) {
    *response = request;

    return true;
}

客户端:

cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QTcpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    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,会在客户端收到完整的响应数据后触发

例如:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QNetworkAccessManager>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    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();
    });
}

效果:

相关推荐
LCS-3127 分钟前
Python爬虫实战: 爬虫常用到的技术及方案详解
开发语言·爬虫·python
枫の准大一8 分钟前
【C++游记】List的使用和模拟实现
开发语言·c++·list
qq_4335545415 分钟前
C++深度优先搜素
开发语言·c++·深度优先
小xin过拟合2 小时前
day20 二叉树part7
开发语言·数据结构·c++·笔记·算法
EstrangedZ2 小时前
vscode(MSVC)进行c++开发的时,在debug时查看一个eigen数组内部的数值
c++·ide·vscode
啟明起鸣2 小时前
【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现
c语言·开发语言·数据结构
乌萨奇也要立志学C++3 小时前
【C++详解】哈希表概念与实现 开放定址法和链地址法、处理哈希冲突、哈希函数介绍
c++·哈希算法·散列表
十八旬3 小时前
苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
java·开发语言·spring boot·mysql·idea·苍穹外卖
这周也會开心3 小时前
Java-多态
java·开发语言