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

效果:

相关推荐
被摘下的星星7 分钟前
Java的类加载
java·开发语言
云栖梦泽10 分钟前
Linux内核与驱动:7.从应用层 lseek() 到驱动层 .llseek,Linux 字符设备偏移控制详解
linux·c++
skilllite作者10 分钟前
SkillLite 多入口架构实战:CLI / Python SDK / MCP / Desktop / Swarm 一页理清
开发语言·人工智能·python·安全·架构·rust·agentskills
秋月的私语16 分钟前
遥感影像拼接线优化工具:基于Qt+GDAL+OpenCV的从零到一实践
开发语言·qt·opencv
steins_甲乙18 分钟前
从0做一个小型内存泄露检测器(2): elf文件的动态链接
c++
xwz小王子24 分钟前
智元发布 GO-2:动作空间推理 + 全生命周期闭环,让机器人稳定可靠落地
开发语言·golang·机器人
charlie11451419124 分钟前
通用GUI编程技术——图形渲染实战(二十八)——图像格式与编解码:PNG/JPEG全掌握
开发语言·c++·windows·学习·图形渲染·win32
ZC跨境爬虫27 分钟前
海南大学交友平台登录页开发实战day4(解决python传输并读取登录信息的问题)
开发语言·前端·python·flask·html
wjs202430 分钟前
SQL LEN() 函数详解
开发语言
姓刘的哦35 分钟前
Qt自定义控件
开发语言·qt