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

目录

(一)UDP Socket

1.1 核心API概览

 1.2  代码示例

1.2.1 回显服务器

1.2.2 回显客户端 

 (二)TCP Socket

2.1 核心API概览

2.2 代码示例

2.2.1 回显服务器

2.2.2 回显客户端

(三)HTTP Client

3.1 核心API

3.2 代码示例 

(四)其他模块

总结


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

注意: 

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

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


(一)UDP Socket

1.1 核心API概览

主要的类有两个. QUdpSocket QNetworkDatagram

QUdpSocket 表示⼀个UDP的socket⽂件.

QNetworkDatagram 表⽰⼀个UDP数据报.


 1.2  代码示例

1.2.1 回显服务器

  • 1) 创建界面,包含⼀个 QListWidget 用来显示消息.

  • 2) 创建 QUdpSocket 成员 .修改widget.h
#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后续的初始化

  • ⼀般来说,要先连接信号槽,再绑定端口.
  • 如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了.此时还没来得及连接信号槽.那么这 个请求就有可能错过了. 
#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,完成处理请求的过程 

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
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⽅法中并没有包含实质性的内容. 
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和端口

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

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

#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

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函数,实现发送请求. 

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概览

核心类是两个: QTcpServer QTcpSocket

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

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

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

例如:

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

2.2 代码示例

2.2.1 回显服务器

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

2) 创建 QTcpServer 并初始化 修改widget.h,添加 QTcpServer 指针成员 

 class Widget : public QWidget
 {
     Q_OBJECT
 public:
     Widget(QWidget *parent = nullptr);
     ~Widget();
 private:
     Ui::Widget *ui;

     //创建QTcpServer 
    QTcpServer* tcpServer;
 };

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

  • 设置窗口标题
  • 实例化TCPserver.(父元素设为当前控件,会在父元素销毁时被⼀起销毁).
  • 通过信号槽,处理客户端建立的新连接.
  • 监听端口
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.
  • 通过信号槽,处理收到请求的情况
  • 通过信号槽,处理断开连接的情况
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⽅法中并没有包含实质性的内容.

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,定义成员

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

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函数,实现发送请求. 

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 属性

#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,创建实例

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请求功能. 

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用于处理服务器返回的数据。

### 在无英伟达显卡环境下的大模型训练与部署 尽管Nvidia GPU因其CUDA生态系统和强大的并行计算能力成为大模型训练的首选设备[^1],但在某些情况下无法使用Nvidia显卡时,仍然存在其他可行的替代方案。以下是几种可能的选择: #### 1. 使用AMD GPU AMD提供了基于ROCm平台的支持,这是一个开源软件栈,旨在为异构计算提供全面解决方案。虽然目前ROCm的功能相较于CUDA仍有一定差距,但它已经能够支持部分主流深度学习框架(如PyTorch和TensorFlow)。对于希望利用非Nvidia硬件的大规模项目来说,这是值得尝试的方向之一。 ```bash # 安装ROCM工具链示例命令 sudo apt-get update && sudo apt-get install rocm-dkms hipblas hipsparse miopen-hip ``` 需要注意的是,在实际操作过程中可能会遇到兼容性和性能优化方面的问题[^3]。 #### 2. 利用Intel CPU进行推理或轻量级训练 当完全缺乏任何类型的GPU资源时,可以考虑仅依靠现代多核处理器来进行工作负载处理。英特尔最新一代至强可扩展处理器通过集成AVX-512指令集等方式显著提升了浮点运算效率,从而使得即使是在纯CPU环境中也能完成一些较为简单的机器学习任务[^4]。 然而值得注意的是,相比于专门设计用于矩阵乘法加速的任务导向型图形芯片而言,传统通用目的中央处理器通常会表现出较低的数据吞吐率以及更长的整体执行时间长度。因此这种方法更适合应用于小型网络或者只需要少量参数调整的情况之下。 #### 3. Google TPU作为云端选项 如果本地基础设施受限,则可以选择借助云服务提供商所供应的专业化ASIC产品——比如谷歌推出的张量处理单元(Tensor Processing Unit,简称TPU)来满足相应的需求。这些定制化的集成电路专为神经网络而生,并且往往能够在单位成本下实现超越常规GPUs的表现水平[^5]。 不过也要意识到采用此类外部依赖意味着额外开销费用增加的同时还伴随着潜在的安全隐患考量等因素影响最终决策制定过程之中。 --- ### 结论 综上所述,即便面临没有可用NVIDIA GPUs的情形下依旧存在着多种途径可供探索实践以达成目标效果;无论是转投竞争对手阵营寻求技术支持还是充分利用现有条件挖掘潜力均不失为明智之举。当然每种策略背后都伴随特定优劣势权衡取舍需谨慎评估后再做定夺。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

起飞的风筝

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

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

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

打赏作者

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

抵扣说明:

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

余额充值