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

效果:

相关推荐
Re.不晚36 分钟前
JAVA进阶之路——无奖问答挑战3
java·开发语言
代码游侠37 分钟前
C语言核心概念复习——C语言基础阶段
linux·开发语言·c++·学习
㓗冽1 小时前
60题之内难题分析
开发语言·c++·算法
dingdingfish1 小时前
Bash学习 - 第3章:Basic Shell Features,第5节:Shell Expansions
开发语言·学习·bash
rainbow68891 小时前
C++开源库dxflib解析DXF文件实战
开发语言·c++·开源
deepxuan1 小时前
Day7--python
开发语言·python
John_ToDebug1 小时前
Chromium安全架构深度解析:从悬空指针检测到内存安全防御体系
c++·chrome
D_evil__1 小时前
【Effective Modern C++】第五章 右值引用、移动语义和完美转发:24. 区分万能引用和右值引用
c++
禹凕1 小时前
Python编程——进阶知识(多线程)
开发语言·爬虫·python
蜡笔小马1 小时前
10.Boost.Geometry R-tree 空间索引详解
开发语言·c++·算法·r-tree