【Qt】网络

发布于:2025-04-04 ⋅ 阅读:(18) ⋅ 点赞:(0)

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:了解 QT 中的网络。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:QT从基础到入门_დ旧言~的博客-CSDN博客

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

一、Udp Socket

主要的类有两个:QUdpSocket 和 QNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件:

QNetworkDatagram 表示一个 UDP 数据报:

代码示例:回显服务器

创建界面,包含一个 QListWidget 用来显示消息

widget.hpp:

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QUdpSocket>
#include <QMessageBox>
#include <QNetworkDatagram>
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 
    void processRequest();
    QString process(const QString& request);
 
private:
    Ui::Widget *ui;
    QUdpSocket* socket;
};
#endif // WIDGET_H

widget.cpp:

先连接信号槽,再绑定端口。若顺序反过来,可能会出现端口绑定好之后,立即接收到请求,但此时还没来得及连接信号槽,那么这个请求就有可能错过了。

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    setWindowTitle("服务器");
    //实例化socket
    socket = new QUdpSocket(this);
    //连接信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
    //绑定端口
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if(!ret) {
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::processRequest()
{
    //读取请求
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    //根据请求计算响应
    const QString& response = process(request);
    //将响应写回客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    //打印日志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" + \
            QString::number(requestDatagram.senderPort()) + "] request:" + \
            request + ", response:" + response;
    ui->listWidget->addItem(log);
}
 
//本代码实现的是回显服务器,所以process方法中不包含实质性的业务代码
QString Widget::process(const QString &request) {
    return request;
}

代码示例:回显客户端

创建界面,包含一个 QLineEdit、QPushButton、QListWidget

widget.hpp:

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QUdpSocket>
#include <QMessageBox>
#include <QNetworkDatagram>
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
    void sendRequest();
    void recvResponse();
 
private:
    Ui::Widget *ui;
    QUdpSocket* socket;
};
#endif // WIDGET_H

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
 
const QString SERVER_IP = "127.0.0.1";
const qint16 SERVER_PORT = 9090;
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("客户端");
    //实例化socket
    socket = new QUdpSocket(this);
    //连接按钮的信号槽
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::sendRequest);
    //连接socket的信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::recvResponse);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::sendRequest()
{
    //获取输入框的内容
    const QString& text = ui->lineEdit->text();
    //构造请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    //发送请求
    socket->writeDatagram(requestDatagram);
    //消息添加到列表框中
    ui->listWidget->addItem("客户端:" + text);
    //清空输入框
    ui->lineEdit->setText("");
}
 
void Widget::recvResponse()
{
    //读取请求
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    //消息添加入列表
    ui->listWidget->addItem(QString("服务器:") + response);
}

二、Tcp Socket

核心类是两个:QTcpServer 和 QTcpSocket

QTcpServer 用于监听端口和获取客户端连接:

QTcpSocket 用于客户端和服务器之间的数据交互:

QByteArray 用于表示一个字节数组,可以很方便的和 QString 进行相互转换:

  • 使用 QString 的构造函数即可把 QByteArray 转成 QString
  • 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray

代码示例:回显服务器

创建界面,包含一个 QListWidget,用于显示收到的数据。

widget.h:

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMessageBox>
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 
    void processConnection();
    QString process(const QString&);
 
private:
    Ui::Widget *ui;
    QTcpServer* tcpServer;
};
#endif // WIDGET_H

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服务端");
    //实例化tcpServer
    tcpServer = new QTcpServer(this);
    //信号槽处理新连接
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
    //监听端口
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);
    if(!ret) {
        QMessageBox::critical(nullptr, "服务器启动出错", tcpServer->errorString());
        return;
    }
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::processConnection()
{
    //获取新连接对应的socket
    QTcpSocket* socket = tcpServer->nextPendingConnection();
    //打印日志
    QString log = "[" + socket->peerAddress().toString() + ":" + \
            QString::number(socket->peerPort()) + "]客户端上线";
    ui->listWidget->addItem(log);
 
    //通过信号槽处理接收到的请求
    connect(socket, &QTcpSocket::readyRead, [=]() {
        //读取请求
        QString request = socket->readAll();
        //根据请求处理响应
        QString response = process(request);
        //将响应写回客户端
        socket->write(response.toUtf8());
        //打印日志
        QString log = QString("[") + socket->peerAddress().toString() \
                + ":" + QString::number(socket->peerPort()) + "] request: " + \
                request + ", response: " + response;
        ui->listWidget->addItem(log);
    });
 
    //通过信号槽处理断开连接的情况
    connect(socket, &QTcpSocket::disconnected, this, [=]() {
        QString log = QString("[") + socket->peerAddress().toString() \
                + ":" + QString::number(socket->peerPort()) + "] 客⼾端下线";
        ui->listWidget->addItem(log);
        // 删除 socket
        socket->deleteLater();
    });
}
 
QString Widget::process(const QString& request) {
    return request;
}

删除socket时最好不要直接使用delete,而是使用deleteLate:

  • 因为整个槽函数都是围绕socket进行操作的,务必确保delete是函数中的最后一步。使用deleteLater更加保险,其不会立即销毁socket,而是在下一轮事件循环中再进行销毁操作。槽函数都是在事件循环中执行的,进入到下一轮事件循环,意味着上一轮事件循环肯定结束了(即槽函数肯定执行完成了)

代码示例:回显客户端

创建界面,包含一个 QLineEdit、QPushButton、QListWidget。

widget.h:

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QTcpSocket>
#include <QMessageBox>
#include <QDebug>
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 
    void processRequest();
    void processResponse();
 
private:
    Ui::Widget *ui;
    QTcpSocket* socket;
};
#endif // WIDGET_H

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("客户端");
    //实例化socket
    socket = new QTcpSocket(this);
    //与服务端建立连接
    socket->connectToHost("127.0.0.1", 9090);
    //等待并确认连接是否出错
    if(!socket->waitForConnected()) {
        QMessageBox::critical(nullptr, "连接服务器出错", socket->errorString());
        exit(1);
    }
    //信号槽处理接收请求并响应
    connect(socket, &QTcpSocket::readyRead, this, &Widget::processResponse);
    //信号槽处理发送请求
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::processRequest);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::processRequest()
{
    //获取输入框中的内容
    const QString& text = ui->lineEdit->text();
    //清空输入框中的内容
    ui->lineEdit->setText("");
    //将消息显示在界面上
    ui->listWidget->addItem(QString("客户端:") + text);
    //将消息发送给服务端
    socket->write(text.toUtf8());
}
 
void Widget::processResponse()
{
    QString response = socket->readAll();
    ui->listWidget->addItem(QString("服务端:") + response);
}

三、HTTP

关键类主要是三个:QNetworkAccessManager、QNetworkRequest、QNetworkReply

QNetworkAccessManager 提供了 HTTP 的核心操作:

QNetworkRequest 表示一个 HTTP请求(不含body):

  • 若需要发送一个带有 body 的请求(如post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body

QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:

QNetworkReply 表示一个 HTTP 响应,这个类同时也是 QIODevice 的子类:

QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据之后触发。

代码示例:HTTP客户端

  • 创建界面,包含一个 QLineEdit、QPushButton、QPlainTextEdit。
  • 此处建议使用 QPlainTextEdit 而不是 QTextEdit,主要因为 QTextEdit 要进行富文本解析,若得到的 HTTP 响应体积过大,会导致界面渲染缓慢甚至被卡住。

widget.h:

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 
    void processRequest();
 
private:
    Ui::Widget *ui;
    QNetworkAccessManager* manager;
};
#endif // WIDGET_H

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("客户端");
    //实例化manager
    manager = new QNetworkAccessManager(this);
    //信号槽处理发送请求
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::processRequest);
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::processRequest()
{
    //获取输入框中url
    QUrl url(ui->lineEdit->text());
    //构造HTTP请求
    QNetworkRequest request(url);
    //发送GET请求
    QNetworkReply* response = manager->get(request);
 
    //通过信号槽处理响应
    connect(response, &QNetworkReply::finished, [=]() {
        if(response->error() == QNetworkReply::NoError) {
            QString html(response->readAll());
            ui->plainTextEdit->setPlainText(html);
        }
        else {
            ui->plainTextEdit->setPlainText(response->errorString());
        }
        response->deleteLater();
    });
}

四、结束语

今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。


网站公告

今日签到

点亮在社区的每一天
去签到