当前位置: 首页 > news >正文

QT跨平台应用程序开发框架(11)—— Qt网络编程

目录

一,概述

二,UDP

2.1 API 介绍

2.2 回显服务器

2.3 回显客户端

2.4 演示

三,TCP

3.1 API 介绍

3.2 回显服务器

3.3 回显客户端

3.4 演示 

四,HTTP

4.1 API 介绍

4.2 http客户端


计算机网络可以参考:https://blog.csdn.net/aaqq800520/category_12705783.html

一,概述

支持网络的操作系统也都会一共一组 API(socket API)来供用户使用,但是 C++ 至今还没有提供一套封装了网络编程的 API,被业界很多人吐槽,所以后面我们将使用 Qt 自己封装的网络 API

  • 我们编写的网络程序,需要传输层支持,所以Qt 也就提供了两套 API,分别针对 UDP 和 TCP
  • 使用 Qt 的网络 API,需要先在 .pro 文件添加 network 模块,我们前面介绍的各种控件,都是包含在 QtCore 模块中的,因为这个模块默认添加

关于模块化,下面来简单概括一下:

  • Qt 是一个体量非常大的框架,如果直接把其所有的功能放到一起,那么搞一个程序就会包含大量没有用到的东西,造成浪费
  • 所以Qt 就把这些功能分成一个一个的模块,要用哪个就添加哪个即可,能极大节约成本 

下面直接开始实操 

二,UDP

2.1 API 介绍

主要涉及的类是两个,QUdpSocketQNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件,核心方法如下:

API类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的端口号bind
receiveDatagram()方法返回 QNetworkDatagram,是一个 UDP 数据包对象recvfrom
writeDatagram(const QNetworkDatagram&)方法

发送一个数据包

sendto
readyRead信号

收到数据并准备就绪后触发该信号

  • 我们在 Linux 上是直接通过阻塞式地等待客户端发小
  • Qt 的这个类似 IO 的多路复用机制

QNetworkDatagram 表示一个 UDP 数据报,核心方法如下:

名称类型说明对标原生 API
QNetworkDatagram(const QByteArray&, const  QHostAddress&, quint16)构造函数通过 QByteArray,添加目标IP、目标端口,构造一个 UDP 数据报,通常用于发送数据
data()方法获取数据报内部持有的数据,返回 QByteArray
senderAddress()方法获取数据包中包含的对端的 IP 地址无,recvfrom 包含了该功能
senderPort()方法获取数据报中包含的对端的端口号无,recvfrom 包含了该功能

2.2 回显服务器

关于回显服务器:

回显服务器是指用于测试和验证网络连接的服务器。它的主要功能是返回客户端发送的数据,以便客户端确认网络连接是否正常。回显服务器一般是一个简单的程序,它接收来自客户端的数据,并将该数据原样返回给客户端。回显服务器的工作原理如下:

  • 当客户端向回显服务器发送数据时,服务器将接收到的数据存储起来,然后将它原样返回给客户端
  • 客户端收到服务器返回的数据后,可以比对原始数据和返回数据是否一致,从而验证网络连接的准确性和稳定性

回显服务器常用于网络测试和诊断,可以帮助用户判断网络是否通畅。例如:

  • 在网络故障排查时,可以使用回显服务器来测试网络连接是否正常
  • 此外,回显服务器还可以用于测试服务器的性能和负载能力,帮助管理员监控网络性能

在实际应用中,回显服务器有多种实现方式:

  • 一种常见的方式是使用简单的套接字编程来实现回显服务器,通过编写程序来进行数据的接收和返回
  • 另一种方式是使用专门的回显服务器软件,这些软件提供了更多的功能和配置选项,能够更好地满足用户的需求

总之,回显服务器是一种用于测试和验证网络连接的服务器,它通过返回客户端发送的数据来确认网络连接的正常性,常用于网络测试、诊断以及性能监控。

来源:回显服务器是什么 • Worktile社区

我们先新建一个基于 QWidget 的项目,名称改为 UdpServer,并创建一个 List Widget用来显示消息:

然后就是添加 Udp 头文件,但是前面说过,需要在 .pro 文件添加 network 才行,如下:

然后就是在 widget.h 文件里添加定义了:

widget.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);socket = new QUdpSocket(this); //创建实例,并且也可以挂对象树上,所以用 this 构造this->setWindowTitle("服务器"); //设置窗口标题connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);//我们需要先连接信号槽再绑定端口号,这就好比要开店,得先装修好,如果没装修好就开业,那就完了bool ret = socket->bind(QHostAddress::Any, 8080); //端口有效范围是1 ~ 65535,是十六位二进制最小和最大值if(!ret){QMessageBox::critical(this, "服务器启动出错", socket->errorString());return;}}Widget::~Widget()
{delete ui;
}
//该函数负责服务器核心逻辑,包括:
//1,读取请求并解析
//2,根据请求计算响应
//3,将响应发回客户端
//其实就是那一套,基本没变,应该说绝大部分服务器都是这样的
void Widget::processRequest() 
{//1,读取解析请求const QNetworkDatagram& requestDatagram = socket->receiveDatagram();QString request = requestDatagram.data(); //返回一个 QByteArray,可以用来赋值和构造 QString(优势)//2,根据请求计算响应(由于是回显服务器,所以不需要计算响应,就是请求本身)const QString& response = process(request);//3,把响应写回客户端QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());//上面有三个参数,第一个表示取出 QS听 内部的字节数组;第二个表示目的IP,第三个表示目的端口socket->writeDatagram(responseDatagram); //和文件描述符一样,所以是 writeQString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())+ "] req: " + request + ", resp: " + response;ui->listWidget->addItem(log); //显示到界面上
}QString Widget::process(const QString &request)
{//回显服务器,响应和请求一样return request;
}

2.3 回显客户端

我们另外搞一个项目,和上面一样,命名为 UdpClient,然后创建下列控件:

UdpClient 项目的 widget.cpp 内容如下:

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>//地址和端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080; //quint16 就是一个 unsigned short,上一个2字节的无符号整数Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);socket = new QUdpSocket(this);this->setWindowTitle("客户端");connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//1,获取到输入框的内容const QString& text = ui->lineEdit->text();//2,构造 UDP 的请求数据QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);//将QString类型的 ip 转为合适的类型//3,发送请求数据socket->writeDatagram(requestDatagram);//4,把发送的请求也添加到列表框中ui->listWidget->addItem("客户端说:" + text);//5,把输入框的内容也清空一下ui->lineEdit->setText("");
}void Widget::processResponse() //这个函数来处理收到的响应
{//1,读取到响应数据const QNetworkDatagram& responseDatagram = socket->receiveDatagram();QString response = responseDatagram.data(); //能用引用尽量用引用,但是涉及到不同类型转换时,还是要用值//2,把响应数据显示到界面上ui->listWidget->addItem("服务器说:" + response);
}

2.4 演示

三,TCP

3.1 API 介绍

  • UDP:无连接,不可靠传输,面向数据包,全双工
  • TCP:有连接,可靠传输,面向字节流,全双工

所以,TCP的代码比UDP多出很多

主要涉及两个类,QTcpServerQTcpSocket

QTcpServer 用于监听端口,和获取客户端连接,核心方法如下:

名称类型说明对标原生 API
listen(const  QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听bind 和 listen
nextPendingConnection()方法
  • 从系统中获取到一个已经建立好的 tcp 连接
  • 返回一个 QTcpSocket对象,表示这个客户端的连接
  • 通过这个socket对象完成和客户端之间的通信
accept
newConnection信号无,但是类似 IO 多路复用的通知机制

QTcpSocket 用于客户端和服务器之间的数据交互,提供的核心方法如下:

名称类型说明对标原生 API
readAll()方法读取当前接收缓冲区的所有数据,返回一个 QByteArray 对象read
write(const QByteArray&)方法把数据写入 socket 中write
deleteLater方法

暂时把socket对象标记为无效

Qt 会在下个事件循环中析构释放该对 象

无,但是类似于半自动化的垃圾回收
readyRead信号有数据到达并准备就绪时触发无,但是类似 IO 多路复用的通知机制
disconnected信号连接断开时触发无,但是类似 IO 多路复用的通知机制

3.2 回显服务器

和 UDP 一样,兴建一个命名为 TcpServer 的项目,创建一个 ListWidget 控件用来显示 log

先在 por 文件里加上netowrk,然后在 .h 文件里声明函数等:

下面是 widget.cpp 的内容:

#include "widget.h"
#include "ui_widget.h"
#include <QTcpSocket>
#include <QMessageBox>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//1,修改窗口标题.this->setWindowTitle("服务器");//2,创建 QTcpServer 的实例tcpServer = new QTcpServer(this);//3,指定如何处理连接.connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);//4,绑定并监听端口号,需要把前面初始化啊全完成才能开始监听端口,要开店得先装修好bool ret = tcpServer->listen(QHostAddress::Any, 8080); //表示愿意接收任何 ip,端口为8080if (!ret) {QMessageBox::critical(this, "服务器启动失败!", tcpServer->errorString()); //弹出错误对话框exit(1);}
}Widget::~Widget()
{delete ui;
}void Widget::processConnection()
{//1,通过 tcpServer 拿到一个 socket 对象, 用来和客户端进行通信.QTcpSocket* clientSocket = tcpServer->nextPendingConnection(); //每个客户端都有一个对象,所以可能有多个,所以当断开连接时必须要释放QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端成功连接";ui->listWidget->addItem(log);//2,处理客户端发来的请求connect(clientSocket, &QTcpSocket::readyRead, this, [=]() //使用 lamdba 表达式{QString request = clientSocket->readAll(); //读取请求内容,返回QByteArray,转成 QStringconst QString& response = process(request); //处理请求,构建响应,返回要发回的内容clientSocket->write(response.toUtf8()); //将响应发回客户端QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] "+ " req: " + request + ", resp: " + response; ui->listWidget->addItem(log); //打印日志//上面的处理办法比较简陋,因为一个完整的请求可能是分成多端字节组进行传输,简单来说就是可能每一次收到的内容都不完整(粘包问题)//但是作为回显服务器已经足够,更好的做法是将每次收到的数据包都放到一个大的缓冲区中,并提前约定好应用层协议的格式,再进行更细致的解析//该步骤在主页的 http服务器 项目里已经实现过,这里就从简了});//3,处理断开连接的情况.connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端断开连接";ui->listWidget->addItem(log); //打印断开连接日志//手动释放 clientSocket // delete clientSocket; //直接使用 delete 不好,有很多限制,比如要保证 delete 是最后一步,并且不能被 return 或抛异常等操作跳过,clientSocket->deleteLater(); //Qt 提供的,不立马销毁,告诉 Qt 在下一轮事件循环再销毁//槽函数都是在事件循环执行的,进入到下一轮事件循环,表示上一轮循环中要做的事已经全部做完了,也表示槽函数已经结束了});
}//回显服务器.
QString Widget::process(const QString request)
{return request;
}

3.3 回显客户端

我们再新建一个 QWidget 的项目,命名为 TcpClient,并创建和 UdpClient 一样的控件

先在 pro 文件里添加network,下面是 .h 的内容:

下面是 widget.cpp 的内容:

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//1,设置窗口标题this->setWindowTitle("客户端");//2,创建 socket 对象的实例socket = new QTcpSocket(this);//3,和服务器建立连接 (这个不是立马连接,因为有三次握手,此处只是发起连接请求,三次握手交给 tcp做的)socket->connectToHost("127.0.0.1", 8080);//4,连接信号槽, 处理响应connect(socket, &QTcpSocket::readyRead, this, [=]() {QString response = socket->readAll(); //读取响应内容ui->listWidget->addItem("服务器说: " + response); //打印内容});//5,等待连接建立的结果,确认是否连接成功bool ret = socket->waitForConnected();if (!ret){QMessageBox::critical(this, "连接服务器出错", socket->errorString());exit(1);}
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{const QString& text = ui->lineEdit->text(); //获取输入框内容socket->write(text.toUtf8()); //将内容发给服务器ui->listWidget->addItem("客户端说: " + text); //显示发送的内容ui->lineEdit->setText(""); //清空输入框
}

3.4 演示 

当服务器未启动直接启动客户端时,会弹出窗口:

四,HTTP

4.1 API 介绍

主要涉及的类有三个:QNetworkAccessManagerQNetworkRequestQNetworkReply

QNetworkAccessManager 提供了 Http 的两个核心操作,如下:

方法说明
get(const QNetworkRequest&)发起一个 HTTP GET 请求,返回 QNetworkReply 对象
post(const QNetworkRequest&, const QByteArray&)发起一个 HTTP POST 请求,返回 QNetworkReply 对象

 QNetworkRequest 这个类表示一个 Http 请求报头,不含请求正文,方法如下:

方法说明
QNetworkRequest(const QUrl&)通过 URL 构造 HTTP 请求
setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value)设置请求头

QNetworkRequest::KnownHeaders 是一个描述请求正文的枚举类型,常用取值如下:

取值说明
ContentTypeHeader描述正文类型
ContentLengthHeader描述正文长度
LocationHeader用于重定向,响应报文中使用
CookieHeader设置 cookie
UserAgentHeader设置 User-Agent

QNetworkReply 表示一个 http 响应,常用方法如下:

方法说明
error()获取错误状态
errorString()获取出错原因
readAll()读取响应文本
header(QNetworkRequest::Known)读取响应指定的header的值

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

4.2 http客户端

注意,Qt 只提供了 http客户端,而没有提供 http服务器的库,所以服务器直接用 www.baidu.com 或者其它网站即可

首先也是和上面的 Udp 和 Tcp 一样,新建一个项目,添加相同控件,头文件如下: 

下面是 widget.cpp 的内容

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkReply>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);this->setWindowTitle("客户端");manager = new QNetworkAccessManager(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//1,获取到输入框中的 urlQUrl url(ui->lineEdit->text());//2,构造一个 HTTP 请求对象QNetworkRequest request(url);//3,发送请求QNetworkReply* response = manager->get(request);//4,通过信号槽, 来处理响应connect(response, &QNetworkReply::finished, this, [=]() {if (response->error() == QNetworkReply::NoError) // 正确获取响应{QString html = response->readAll();ui->plainTextEdit->setPlainText(html); //不用 QTextEdit,因为其会对 html 进行解析,就不知原始的 html 代码了}else ui->plainTextEdit->setPlainText(response->errorString());  //响应错误// 释放responseresponse->deleteLater();});
}

http://www.dtcms.com/a/297064.html

相关文章:

  • [NLP]一个完整的 UPF 文件示例
  • Vim 编辑器全模式操作指南
  • 纳米编辑器之Nano 编辑器退出**的详细操作指南
  • 【MacOS】发展历程
  • Linux 中 `chown`、`chgrp` 和 `chmod` 命令详解
  • openGauss数据库在CentOS 7 中的单机部署与配置
  • VMware虚拟出来的centos中设置静态ip
  • fish-speech 在50系列显卡使用 --compile加速兼容
  • Rust + Tauri 开发所需环境清单(以 Windows 为例)
  • 【unitrix】 6.16 非负整数类型( TUnsigned )特质(t_unsingned.rs)
  • [Rust 基础课程]猜数字游戏-获取用户输入并打印
  • 智能问答分类系统:基于SVM的用户意图识别
  • 弹性网:基于神经网络的多组分磁共振弹性成像波反演与不确定性量化|文献速递-医学影像算法文献分享
  • 奥比中光的dabai_dcw2相机彩色对齐方案
  • Android Camera setRepeatingRequest
  • 11. isaacsim4.2教程-Transform 树与Odometry
  • Java面试题(中等)
  • Cartographer安装测试与模块开发(三)--Cartographer在Gazebo仿真环境下的建图以及建图与定位阶段问题(实车也可参考)
  • 融合与智能:AI 浪潮驱动下数据库的多维度进化与产业格局重塑新范式
  • 深入解析Linux匿名管道机制与应用
  • 从数据孤岛到融合共生:KES V9 2025 构建 AI 时代数据基础设施
  • Lua 函数
  • JAVA_THIRTEEN_常用API
  • 星慈光编程虫2号小车讲解第三篇--附件概述
  • ai存在意义的对话
  • 从零开发Java坦克大战:架构设计与难点突破 (上)
  • 星慈光编程虫2号小车讲解第二篇--向左向右平移
  • 处理URL请求参数:精通`@PathVariable`、`@RequestParam`与`@MatrixVariable`
  • 结构化布线系统详解
  • Datawhale AI 夏令营-心理健康Agent开发学习-Task2.1