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_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;
}

客户端:

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

服务器:

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

客户端:

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

例如:

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

效果:

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forward♞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值