【Qt网络】—— Qt网络编程

目录

[(一)UDP Socket](#(一)UDP Socket)

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

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

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

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

[(二)TCP Socket](#(二)TCP Socket)

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

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

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

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

[(三)HTTP Client](#(三)HTTP Client)

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

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

(四)其他模块

总结


和多线程类似,Qt为了支持跨平台,对网络编程的API也进行了重新封装。咱们接下来的重点介绍Qt的网络相关的API的使用。

注意:

  • 实际Qt开发中进行网络编程,也不⼀定使用Qt封装的网络API,也有⼀定可能使用的是系统原⽣API或者其他第三方框架的API.

在进行网络编程之前,需要在项目中的 .pro 文件中添加 network 模块. 添加之后要手动编译⼀下项目,使QtCreator能够加载对应模块的头文件.


(一)UDP Socket

1.1 核心API概览

主要的类有两个. QUdpSocketQNetworkDatagram

QUdpSocket表示⼀个UDP的socket⽂件.

QNetworkDatagram表⽰⼀个UDP数据报.


1.2 代码示例

1.2.1 回显服务器

  • 1) 创建界面,包含⼀个 QListWidget 用来显示消息.
  • 2) 创建 QUdpSocket 成员 .修改widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void handle();
    QString process(const QString& request);
private:
    Ui::Widget *ui;

    QUdpSocket* socket;

};
#endif // WIDGET_H

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

  • ⼀般来说,要先连接信号槽,再绑定端口.
  • 如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了.此时还没来得及连接信号槽.那么这 个请求就有可能错过了.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#include<QNetworkDatagram>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗⼝标题
    this->setWindowTitle("服务器");
    //实例化 socket
    socket = new QUdpSocket(this);
    //连接信号槽
    connect(socket,&QUdpSocket::readyRead,this,&Widget::handle);

    //绑定端口号
    bool res = socket->bind(QHostAddress::Any,9090);
    if (!res) {
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        return;
    }
}

Widget::~Widget()
{
    delete ui;
}

3)实现handle,完成处理请求的过程

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
cpp 复制代码
void Widget::handle()
{
    // 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);
}

4) 实现 process相关功能

  • 由于我们此处是实现回显服务器.所以process⽅法中并没有包含实质性的内容.
cpp 复制代码
QString Widget::process(const QString &request)
{
    return request;
}

到此 服务器程序编写完毕.


1.2.2 回显客户端

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

  • 先使用水平布局把 QPushButton , QLineEdit 和 sizePolicy 为 QListWidget QPushButton 放好,并设置这两个控件的垂直方向的 Expanding •
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

2) 在widget.cpp中,先创建两个全局常量,表示服务器的IP和端口

cpp 复制代码
// 提前定义好服务器的 IP 和 端⼝
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

3) 创建 QUdpSocket 成员 修改widget.h,定义成员

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QUdpSocket>
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 handle();
private:
    Ui::Widget *ui;
    // 创建 socket 成员
    QUdpSocket* socket;
};
#endif // WIDGET_H

修改widget.cpp,初始化socket

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 1. 实例化 socket
    socket = new QUdpSocket(this);
    // 2. 设置窗⼝名字
    this->setWindowTitle("客户端");

    connect(socket,&QUdpSocket::readyRead,this,&Widget::handle);
}

4) 给发送按钮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("");
}

void Widget::handle()
{
    //读取响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    //把响应数据显示到界面上
    ui->listWidget->addItem(QString("服务器说: ") + response);
}

最终执行效果(记住先运行服务器在运行客户端)


(二)TCP Socket

2.1 核心API概览

核心类是两个: QTcpServerQTcpSocket

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

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

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

例如:

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

2.2 代码示例

2.2.1 回显服务器

1)创建界面.包含⼀个 QListWidget ,用于显示收到的数据

2) 创建 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 并进行后续初始化操作.

  • 设置窗口标题
  • 实例化TCPserver.(父元素设为当前控件,会在父元素销毁时被⼀起销毁).
  • 通过信号槽,处理客户端建立的新连接.
  • 监听端口
cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置窗口标题
   this->setWindowTitle("服务器");

    //初始化
    tcpserver = new QTcpServer(this);

    //通过槽函数连接
    connect(tcpserver,&QTcpServer::newConnection,this,&Widget::handle);

    //监听端口
    bool res = tcpserver->listen(QHostAddress::Any,9090);
    if(!res){
        QMessageBox::critical(this,"服务器启动失败",tcpserver->errorString());
        exit(1);
    }
}

3) 继续修改widget.cpp,实现处理连接的具体方法 handle

  • 获取到新的连接对应的socket.
  • 通过信号槽,处理收到请求的情况
  • 通过信号槽,处理断开连接的情况
cpp 复制代码
void Widget::handle()
{
    //获取到新的连接对应的socket.
    QTcpSocket* clientSocket = tcpserver->nextPendingConnection();
    QString log = QString('[') + clientSocket->peerAddress().toString() + ":"
                        + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
    ui->listWidget->addItem(log);

    //通过信号槽,处理收到请求的情况
    connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
       //读取出请求数据
        QString request = clientSocket->readAll();
       //根据请求处理响应
        const QString& response =  process(request);
        //写会客户端
        clientSocket->write(response.toUtf8());

        QString log = QString("[") + clientSocket->peerAddress().toString()
                        + ":" + QString::number(clientSocket->peerPort()) + "] req: " +
                        request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });

    //通过信号槽,处理断开连接的情况
    connect(clientSocket,&QTcpSocket::disconnected,this,[=](){
        QString log = "[" + clientSocket->peerAddress().toString() + ":"
                        + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
        ui->listWidget->addItem(log);
        clientSocket->deleteLater();
    });
}

4) 实现process方法,实现根据请求处理响应. 由 于我们此处是实现回显服务器.所以process⽅法中并没有包含实质性的内容.

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

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


2.2.2 回显客户端

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

  • 先使用水平布局把 QPushButton , QLineEdit 和 sizePolicy 为 QListWidget QPushButton 放好,并设置这两个控件的垂直方向的 Expanding •
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

2) 创建 QUdpSocket 成员 修改widget.h,定义成员

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

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QTcpSocket* socket;
};
#endif // WIDGET_H

修改widget.cpp,初始化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.处理服务器返回的响应.
    connect(socket,&QTcpSocket::readyRead,this,[=](){
        //读取当前接收缓冲区中的所有数据
       QString response = socket->readAll();
       qDebug() << response;
       ui->listWidget->addItem(QString("服务器说: ") + response);
    });
    // 5. 等待并确认连接是否出错
    if(!socket->waitForConnected()){
        QMessageBox::critical(this,"连接服务器出错!",socket->errorString());
        exit(1);
    }
}

3) 给发送按钮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());
}

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


(三)HTTP Client

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

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

3.1 核心API

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

QNetworkAccessManager提供了HTTP的核心操作.

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

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

QNetworkReply表⽰⼀个HTTP响应.这个类同时也是 QIODevice 的⼦类

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


3.2 代码示例

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

  • 先使用水平布局把 QPushButton , QLineEdit 和 sizePolicy 为 QListWidget QPushButton 放好,并设置这两个控件的垂直方向的 Expanding •
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调).

【说明】

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

2) 修改widget.h,创建 QNetworkAccessManager 属性

cpp 复制代码
#include <QWidget>
#include<QNetworkAccessManager>
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;
};
#endif // WIDGET_H

3) 修改widget.cpp,创建实例

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");
    // 实例化属性
    manager = new QNetworkAccessManager(this);
}

Widget::~Widget()
{
    delete ui;
}

4) 编写按钮的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();
    });
}

执行程序,观察效果


(四)其他模块

Qt 中还提供了FTP,DNS,SSL等网络相关的组件工具.此处不再⼀⼀展开介绍.有需要的可以自行翻阅官方文档学习相关API的使用.


总结

Qt是一个跨平台的应用程序和用户界面框架,广泛用于开发图形用户界面程序,同时也提供了强大的网络编程能力。Qt的网络编程主要基于其网络模块,即**QtNetwork**模块。以下是一些关于Qt网络编程的小结:

TCP套接字编程

  • 使用QTcpSocket可以创建客户端和服务器端的TCP连接。客户端通过连接到服务器的IP地址和端口来建立连接,而服务器端则监听特定端口等待客户端的连接请求。

UDP套接字编程

  • QUdpSocket用于处理无连接的网络通信。它允许发送和接收UDP数据包。UDP不保证数据包的顺序或可靠性,但其开销较小,适用于对实时性要求较高的应用。

HTTP请求

  • QNetworkAccessManager是处理HTTP请求的核心类。它支持GET、POST、PUT、DELETE等多种HTTP方法。通过QNetworkRequest可以设置请求的URL、头部信息等,而QNetworkReply用于处理服务器返回的数据。
相关推荐
半盏茶香42 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Evand J2 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB2 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
计算机学长大白3 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
PieroPc4 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439696 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna7 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_7 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar8 小时前
速通Python 第三节
开发语言·python