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 介绍
主要涉及的类是两个,QUdpSocket 和 QNetworkDatagram
QUdpSocket 表示一个 UDP 的 socket 文件,核心方法如下:
发送一个数据包
收到数据并准备就绪后触发该信号
- 我们在 Linux 上是直接通过阻塞式地等待客户端发小
- Qt 的这个类似 IO 的多路复用机制
QNetworkDatagram 表示一个 UDP 数据报,核心方法如下:
2.2 回显服务器
关于回显服务器:
回显服务器是指用于测试和验证网络连接的服务器。它的主要功能是返回客户端发送的数据,以便客户端确认网络连接是否正常。回显服务器一般是一个简单的程序,它接收来自客户端的数据,并将该数据原样返回给客户端。回显服务器的工作原理如下:
- 当客户端向回显服务器发送数据时,服务器将接收到的数据存储起来,然后将它原样返回给客户端
- 客户端收到服务器返回的数据后,可以比对原始数据和返回数据是否一致,从而验证网络连接的准确性和稳定性
回显服务器常用于网络测试和诊断,可以帮助用户判断网络是否通畅。例如:
- 在网络故障排查时,可以使用回显服务器来测试网络连接是否正常
- 此外,回显服务器还可以用于测试服务器的性能和负载能力,帮助管理员监控网络性能
在实际应用中,回显服务器有多种实现方式:
- 一种常见的方式是使用简单的套接字编程来实现回显服务器,通过编写程序来进行数据的接收和返回
- 另一种方式是使用专门的回显服务器软件,这些软件提供了更多的功能和配置选项,能够更好地满足用户的需求
总之,回显服务器是一种用于测试和验证网络连接的服务器,它通过返回客户端发送的数据来确认网络连接的正常性,常用于网络测试、诊断以及性能监控。
来源:回显服务器是什么 • Worktile社区
我们先新建一个基于 QWidget 的项目,名称改为 UdpServer,并创建一个 List Widget用来显示消息:
然后就是添加 Udp 头文件,但是前面说过,需要在 .pro 文件添加 network 才行,如下:
然后就是在 widget.h 文件里添加定义了:
widget.cpp 内容如下:
#include \"widget.h\"#include \"ui_widget.h\"#include#includeWidget::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); //和文件描述符一样,所以是 write QString 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 //地址和端口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多出很多
主要涉及两个类,QTcpServer 和 QTcpSocket
QTcpServer 用于监听端口,和获取客户端连接,核心方法如下:
- 从系统中获取到一个已经建立好的 tcp 连接
- 返回一个 QTcpSocket对象,表示这个客户端的连接
- 通过这个socket对象完成和客户端之间的通信
QTcpSocket 用于客户端和服务器之间的数据交互,提供的核心方法如下:
暂时把socket对象标记为无效
Qt 会在下个事件循环中析构释放该对 象
3.2 回显服务器
和 UDP 一样,兴建一个命名为 TcpServer 的项目,创建一个 ListWidget 控件用来显示 log
先在 por 文件里加上netowrk,然后在 .h 文件里声明函数等:
下面是 widget.cpp 的内容:
#include \"widget.h\"#include \"ui_widget.h\"#include #include 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,端口为8080 if (!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,转成 QString const 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 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 介绍
主要涉及的类有三个:QNetworkAccessManager,QNetworkRequest,QNetworkReply
QNetworkAccessManager 提供了 Http 的两个核心操作,如下:
QNetworkRequest 这个类表示一个 Http 请求报头,不含请求正文,方法如下:
QNetworkRequest::KnownHeaders 是一个描述请求正文的枚举类型,常用取值如下:
QNetworkReply 表示一个 http 响应,常用方法如下:
此外,QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据后触发
4.2 http客户端
注意,Qt 只提供了 http客户端,而没有提供 http服务器的库,所以服务器直接用 www.baidu.com 或者其它网站即可
首先也是和上面的 Udp 和 Tcp 一样,新建一个项目,添加相同控件,头文件如下:
下面是 widget.cpp 的内容
#include \"widget.h\"#include \"ui_widget.h\"#include 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,获取到输入框中的 url QUrl 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()); //响应错误 // 释放response response->deleteLater(); });}