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

效果:

相关推荐
研☆香11 小时前
js中使用的正则表达式
开发语言·javascript·正则表达式
szarron11 小时前
国产手持式频谱分析仪选型攻略:TFN RC系列 vs HTOOL SA8T频谱分析仪 专业参数对比(军工/路测/调试全覆盖)
开发语言·前端·状态模式
我是唐青枫11 小时前
C#.NET StructureMap 从依赖注入到项目实战
开发语言·c#·.net
Beyond_System|系统之外11 小时前
一个网页如何同时适配投影仪、1366×768 和 1920×1080?我重新理解了“自适应布局”
开发语言·css·html
蛋先生DX11 小时前
内存安全翻车现场的罪魁祸首,根源就在这三步里?
c++·后端·编程语言
小七在进步11 小时前
C++入门(1)
java·jvm·c++
晴天的雨.99211 小时前
[C++算法]快乐数
数据结构·c++·算法
Wang's Blog11 小时前
Java框架快速入门: Spring Security+OAuth2之定制登录页
java·开发语言·spring
imgsq11 小时前
MapLibre GL JS v6 正式发布:v5 之后八个月的第一个大版本(编译)
开发语言·javascript·ecmascript
统计学小王子11 小时前
数学建模国赛倒计时 2 天 ——《缺失值处理一(R语言)》
开发语言·数学建模·r语言