【QT系统相关】QT网络

目录

[1. UDP Socket](#1. UDP Socket)

[1.1 核心 API 概览](#1.1 核心 API 概览)

[1.2 回显服务器](#1.2 回显服务器)

[1.3 回显客户端](#1.3 回显客户端)

[2. TCP Socket](#2. TCP Socket)

[2.1 核心 API 概览](#2.1 核心 API 概览)

[2.2 回显服务器](#2.2 回显服务器)

[2.3 回显客户端](#2.3 回显客户端)

[3. HTTP Client](#3. HTTP Client)

[3.1 核心 API](#3.1 核心 API)

[3.2 代码示例](#3.2 代码示例)


QT专栏:QT_uyeonashi的博客-CSDN博客

在进行网络编程之前, 需要在项目中的.pro 文件中添加 network 模块.

添加之后要手动编译一下项目, 使 Qt Creator 能够加载对应模块的头文件

1. UDP Socket

1.1 核心 API 概览

主要的类有两个. QUdpSocket 和 QNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件.

QNetworkDatagram 表示一个 UDP 数据报.

1.2 回显服务器

  1. 创建界面, 包含一个 QListWidget 用来显示消息.
  1. 创建 QUdpSocket 成员

修改 widget.h

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    QUdpSocket* socket;
};

修改 widget.cpp, 完成 socket 后续的初始化

一般来说, 要先连接信号槽, 再绑定端口.

如果顺序反过来, 可能会出现端口绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗口标题
    this->setWindowTitle("服务器");

    // 2. 实例化 socket
    socket = new QUdpSocket(this);

    // 3. 连接信号槽, 处理收到的请求
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 4. 绑定端口
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if (!ret)
    {
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
}
  1. 实现 processRequest , 完成处理请求的过程

• 读取请求并解析

• 根据请求计算响应

• 把响应写回到客户端

cpp 复制代码
void Widget::processRequest()
{
    // 1. 读取请求
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();

    // 2. 根据请求计算响应
    const QString& response = process(request);

    // 3. 把响应写回到客户端
    QNetworkDatagram responseDatagram(response.toUtf8(),
    requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);

    // 显示打印日志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())+ "] req: " + request + ", resp: " +  response;
    ui->listWidget->addItem(log);
}
  1. 实现 process 函数

由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.

cpp 复制代码
QString Widget::process(const QString& request)
{
    return request;
}

"根据请求处理响应" 是服务器开发中的最核心的步骤.

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

1.3 回显客户端

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

• 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的

sizePolicy 为 Expanding

• 再使用垂直布局把 QListWidget 和上面的水平布局放好.

• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

  1. 在 widget.cpp 中, 先创建两个全局常量, 表示服务器的 IP 和 端口
cpp 复制代码
// 提前定义好服务器的 IP 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
  1. 创建 QUdpSocket 成员

修改 widget.h, 定义成员

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    // 创建 socket 成员
    QUdpSocket* socket;
};

修改 widget.cpp, 初始化 socket

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗口名字
    this->setWindowTitle("客户端");
    // 2. 实例化 socket
    socket = new QUdpSocket(this);
}
  1. 给发送按钮 slot 函数, 实现发送请求
cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 1. 获取到输入框的内容
    const QString& text = ui->lineEdit->text();

    // 2. 构造请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP),SERVER_PORT);

    // 3. 发送请求
    socket->writeDatagram(requestDatagram);

    // 4. 消息添加到列表框中
    ui->listWidget->addItem("客户端说: " + text);

    // 5. 清空输入框
    ui->lineEdit->setText("");
}
  1. 再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.
cpp 复制代码
connect(socket, &QUdpSocket::readyRead, this, [=]() {
    const QNetworkDatagram responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    ui->listWidget->addItem(QString("服务器说: ") + response);
});

最终执行效果

启动多个客户端都可以正常工作.

2. TCP Socket

2.1 核心 API 概览

核心类是两个: QTcpServer 和 QTcpSocket

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

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

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

例如:

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

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

2.2 回显服务器

  1. 创建界面. 包含一个 QListWidget , 用于显示收到的数据.
  1. 创建 QTcpServer 并初始化

修改 widget.h, 添加 QTcpServer 指针成员.

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    // 创建 QTcpServer
    QTcpServer* tcpServer;
};

修改 widget.cpp, 实例化 QTcpServer 并进行后续初始化操作.

• 设置窗口标题

• 实例化 TCP server. (父元素设为当前控件, 会在父元素销毁时被一起销毁).

• 通过信号槽, 处理客户端建立的新连接.

• 监听端口

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗口标题
    this->setWindowTitle("服务器");

    // 2. 实例化 TCP server
    tcpServer = new QTcpServer(this);

    // 3. 通过信号槽, 处理客户端建立的新连接.
    connect(tcpServer, &QTcpServer::newConnection, this,&Widget::processConnection);

    // 4. 监听端口
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);
    if (!ret) 
    {
        QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer->errorString());
        exit(1);
    }
}
  1. 继续修改 widget.cpp, 实现处理连接的具体方法 processConnection

• 获取到新的连接对应的 socket.

• 通过信号槽, 处理收到请求的情况

• 通过信号槽, 处理断开连接的情况

cpp 复制代码
void Widget::processConnection()
{
    // 1. 获取到新的连接对应的 socket.
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = QString("[") + clientSocket->peerAddress().toString()+ ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
    ui->listWidget->addItem(log);

    // 2. 通过信号槽, 处理收到请求的情况
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        // a) 读取请求
        QString request = clientSocket->readAll();
        // b) 根据请求处理响应
        const QString& response = process(request);
        // c) 把响应写回客户端
        clientSocket->write(response.toUtf8());
        QString log = QString("[") + clientSocket->peerAddress().toString()+ ":" + QString::number(clientSocket->peerPort()) + "] req: " +request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });

    // 3. 通过信号槽, 处理断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        QString log = QString("[") + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线!";
        ui->listWidget->addItem(log);
        // 删除 clientSocket
        clientSocket->deleteLater();
    });
}
  1. 实现 process 方法, 实现根据请求处理响应.

由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.

cpp 复制代码
QString Widget::process(const QString &request)
{
    return request;
}

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

2.3 回显客户端

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

• 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的

sizePolicy 为 Expanding

• 再使用垂直布局把 QListWidget 和上面的水平布局放好.

• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

  1. 创建 QTcpSocket 并实例化

修改 widget.h, 创建成员.

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    // 新增 QTcpSocket
    QTcpSocket* socket;
};

修改 widget.cpp, 对 QTcpSocket 进行实例化.

• 设置窗口标题

• 实例化 socket 对象 (父元素设为当前控件, 会在父元素销毁时被一起销毁).

• 和服务器建立连接.

• 等待并确认连接是否出错.

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗口标题.
    this->setWindowTitle("客户端");

    // 2. 实例化 socket 对象.
    socket = new QTcpSocket(this);

    // 3. 和服务器建立连接.
    socket->connectToHost("127.0.0.1", 9090);

    // 4. 等待并确认连接是否出错.
    if (!socket->waitForConnected()) 
    {
        QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
        exit(1);
    }
}
  1. 修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器.
cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 获取输入框的内容
    const QString& text = ui->lineEdit->text();

    // 清空输入框内容
    ui->lineEdit->setText("");

    // 把消息显示到界面上
    ui->listWidget->addItem(QString("客户端说: ") + text);

    // 发送消息给服务器
    socket->write(text.toUtf8());
}
  1. 修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应
cpp 复制代码
// 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QString response = socket->readAll();
    qDebug() << response;
    ui->listWidget->addItem(QString("服务器说: ") + response);
});

先启动服务器, 再启动客户端(可以启动多个), 最终执行效果:

由于我们使用信号槽处理同一个客户端的多个请求, 不涉及到循环, 也就不会使客户端之间相互影响了.

3. HTTP Client

进行 Qt 开发时, 和服务器之间的通信很多时候也会用到 HTTP 协议.

• 通过 HTTP 从服务器获取数据.

• 通过 HTTP 向服务器提交数据.

3.1 核心 API

关键类主要是三个. QNetworkAccessManager , QNetworkRequest , QNetworkReply .

QNetworkAccessManager 提供了 HTTP 的核心操作.

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

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

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

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

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

3.2 代码示例

给服务器发送一个 GET 请求.

  1. 创建界面. 包含一个 QLineEdit , QPushButton

• 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的

sizePolicy 为 Expanding

• 再使用垂直布局把 QPlainTextEdit 和上面的水平布局放好. ( QPlainTextEdit 的

readOnly 设为 true )

• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

💡 此处建议使用 QPlainTextEdit 而不是 QTextEdit . 主要因为 QTextEdit 要进行富文本解析, 如果得到的 HTTP 响应体积很大, 就会导致界面渲染缓慢甚至被卡住.

  1. 修改 widget.h, 创建 QNetworkAccessManager 属性
cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    // 新增属性
    QNetworkAccessManager* manager;
};
  1. 修改 widget.cpp, 创建实例
cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化属性
    manager = new QNetworkAccessManager(this);
}
  1. 编写按钮的 slot 函数, 实现发送 HTTP 请求功能
cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 1. 获取到输入框中的 URL, 构造 QUrl 对象
    QUrl url(ui->lineEdit->text());

    // 2. 构造 HTTP 请求对象
    QNetworkRequest request(url);

    // 3. 发送 GET 请求
    QNetworkReply* response = manager->get(request);

    // 4. 通过信号槽来处理响应
    connect(response, &QNetworkReply::finished, this, [=]() {
        if (response->error() == QNetworkReply::NoError) 
        {
            // 响应正确
            QString html(response->readAll());
            ui->plainTextEdit->setPlainText(html);
            // qDebug() << html;
        } 
        else 
        {
            // 响应出错
            ui->plainTextEdit->setPlainText(response->errorString());
        }
        response->deleteLater();
    });
}

执行程序, 观察效果

发送 POST 请求代码也是类似. 使用 manager->post() 即可. 此处不再演示.


本篇完!

相关推荐
Johny_Zhao3 小时前
CentOS Stream 8 高可用 Kuboard 部署方案
linux·网络·python·网络安全·docker·信息安全·kubernetes·云计算·shell·yum源·系统运维·kuboard
知然5 小时前
鸿蒙 Native API 的封装库 h2lib_arkbinder
c++·arkts·鸿蒙
粟悟饭&龟波功5 小时前
Java—— ArrayList 和 LinkedList 详解
java·开发语言
冷雨夜中漫步5 小时前
Java中如何使用lambda表达式分类groupby
java·开发语言·windows·llama
十五年专注C++开发5 小时前
Qt .pro配置gcc相关命令(三):-W1、-L、-rpath和-rpath-link
linux·运维·c++·qt·cmake·跨平台编译
a4576368765 小时前
Objective-c Block 面试题
开发语言·macos·objective-c
Cai junhao6 小时前
【Qt】Qt控件
开发语言·c++·笔记·qt
Simple_core6 小时前
Qt3d中的材质--PBR材质
qt·3d·材质
程序猿小D6 小时前
第27节 Node.js Buffer
linux·开发语言·vscode·node.js·c#·编辑器·vim