Qt:Qt网络

server/2025/2/13 6:00:09/

目录

Qt网络介绍

UDP Socket

TCP Socket

HTTP Client

总结


Qt网络介绍

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

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

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


UDP Socket

主要的类有两个:QUdpSocket 和 QNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件

名称类型说明对标原生API
bind(constQHostAddress&,quint16)方法绑定指定的端⼝号bind
receiveDatagram()方法返回 读取⼀个UDP数据报recvfrom
writeDatagram(const QNetworkDatagram&)方法发送⼀个UDP数据报sendto
readyRead信号在收到数据并准备就绪后触发无(类似于IO多路复用的通知机制)

QNetworkDatagram 表示一个 UDP 数据报

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

代码示例:回显服务器

创建界面,包含一个 QListWidget 用来显示消息

widget.hpp

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QUdpSocket>
#include <QMessageBox>
#include <QNetworkDatagram>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void processRequest();QString process(const QString& request);private:Ui::Widget *ui;QUdpSocket* socket;
};
#endif // WIDGET_H

widget.cpp

先连接信号槽,再绑定端口。若顺序反过来,可能会出现端口绑定好之后,立即接收到请求,但此时还没来得及连接信号槽,那么这个请求就有可能错过了

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);setWindowTitle("服务器");//实例化socketsocket = new QUdpSocket(this);//连接信号槽connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);//绑定端口bool ret = socket->bind(QHostAddress::Any, 9090);if(!ret) {QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());return;}}Widget::~Widget()
{delete ui;
}void Widget::processRequest()
{//读取请求const QNetworkDatagram& requestDatagram = socket->receiveDatagram();QString request = requestDatagram.data();//根据请求计算响应const QString& response = process(request);//将响应写回客户端QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());socket->writeDatagram(responseDatagram);//打印日志QString log = "[" + requestDatagram.senderAddress().toString() + ":" + \QString::number(requestDatagram.senderPort()) + "] request:" + \request + ", response:" + response;ui->listWidget->addItem(log);
}//本代码实现的是回显服务器,所以process方法中不包含实质性的业务代码
QString Widget::process(const QString &request) {return request;
}

代码示例:回显客户端

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

widget.hpp

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QUdpSocket>
#include <QMessageBox>
#include <QNetworkDatagram>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void sendRequest();void recvResponse();private:Ui::Widget *ui;QUdpSocket* socket;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"const QString SERVER_IP = "127.0.0.1";
const qint16 SERVER_PORT = 9090;Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);setWindowTitle("客户端");//实例化socketsocket = new QUdpSocket(this);//连接按钮的信号槽connect(ui->pushButton, &QPushButton::clicked, this, &Widget::sendRequest);//连接socket的信号槽connect(socket, &QUdpSocket::readyRead, this, &Widget::recvResponse);
}Widget::~Widget()
{delete ui;
}void Widget::sendRequest()
{//获取输入框的内容const QString& text = ui->lineEdit->text();//构造请求数据QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);//发送请求socket->writeDatagram(requestDatagram);//消息添加到列表框中ui->listWidget->addItem("客户端:" + text);//清空输入框ui->lineEdit->setText("");
}void Widget::recvResponse()
{//读取请求const QNetworkDatagram& responseDatagram = socket->receiveDatagram();QString response = responseDatagram.data();//消息添加入列表ui->listWidget->addItem(QString("服务器:") + response);
}

TCP Socket

核心类是两个:QTcpServer 和 QTcpSocket

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

名称类型说明对标原生API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号并开始监听bind和listen
nextPendingConnection()方法

从系统中获取一个已经建立好的tcp连接

返回一个TcpSocket,表示这个连接

通过这个socket对象完成与客户端之间的通信

accept
newConnection信号有新的客户端建立好连接后触发无(类似与IO多路复用中的通知机制)

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

名称类型说明对标原生API
readAll()方法读取当前接收缓冲区中的所有数据.
返回QByteArray对象
read
write(constQByteArray&)方法把数据写入socket中write
deleteLater方法暂时把socket对象标记为无效 Qt会在下个事件循环中析构释放该对
无(但是类似于"半自动化的垃圾回收")
readyRead信号有数据到达并准备就绪时触发无(但是类似于IO多路复用中的通知机制)
disconnected信号连接断开时触发无(但是类似于IO多路复用中的通知机制)

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

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

代码示例:回显服务器

创建界面,包含一个 QListWidget,用于显示收到的数据

widget.h

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMessageBox>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void processConnection();QString process(const QString&);private:Ui::Widget *ui;QTcpServer* tcpServer;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);setWindowTitle("服务端");//实例化tcpServertcpServer = new QTcpServer(this);//信号槽处理新连接connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);//监听端口bool ret = tcpServer->listen(QHostAddress::Any, 9090);if(!ret) {QMessageBox::critical(nullptr, "服务器启动出错", tcpServer->errorString());return;}
}Widget::~Widget()
{delete ui;
}void Widget::processConnection()
{//获取新连接对应的socketQTcpSocket* socket = tcpServer->nextPendingConnection();//打印日志QString log = "[" + socket->peerAddress().toString() + ":" + \QString::number(socket->peerPort()) + "]客户端上线";ui->listWidget->addItem(log);//通过信号槽处理接收到的请求connect(socket, &QTcpSocket::readyRead, [=]() {//读取请求QString request = socket->readAll();//根据请求处理响应QString response = process(request);//将响应写回客户端socket->write(response.toUtf8());//打印日志QString log = QString("[") + socket->peerAddress().toString() \+ ":" + QString::number(socket->peerPort()) + "] request: " + \request + ", response: " + response;ui->listWidget->addItem(log);});//通过信号槽处理断开连接的情况connect(socket, &QTcpSocket::disconnected, this, [=]() {QString log = QString("[") + socket->peerAddress().toString() \+ ":" + QString::number(socket->peerPort()) + "] 客⼾端下线";ui->listWidget->addItem(log);// 删除 socketsocket->deleteLater();});
}QString Widget::process(const QString& request) {return request;
}

删除socket时最好不要直接使用delete,而是使用deleteLate

因为整个槽函数都是围绕socket进行操作的,务必确保delete是函数中的最后一步。使用deleteLater更加保险,其不会立即销毁socket,而是在下一轮事件循环中再进行销毁操作。槽函数都是在事件循环中执行的,进入到下一轮事件循环,意味着上一轮事件循环肯定结束了(即槽函数肯定执行完成了)。

代码示例:回显客户端

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

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

再使用垂直布局把QListWidget和上面的水平布局放好
设置垂直布局的layoutStretch为5,1(当然这个尺寸比例根据个人喜好微调)

创建QTcpSocket并实例化

修改widget.h,创建成员

class Widget : public QWidget
{Q_OBJECT
public:Widget(QWidget *parent = nullptr);~Widget();
private:Ui::Widget *ui;// 新增 QTcpSocketQTcpSocket* socket;
};

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

  • 设置窗口标题
  • 实例化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. 等待并确认连接是否出错.if (!socket->waitForConnected()) {QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());exit(1);
}

修改widget.cpp,给按钮增加点击的slot函数,实现发送请求给服务器

void Widget::on_pushButton_clicked()
{// 获取输⼊框的内容const QString& text = ui->lineEdit->text();// 清空输⼊框内容ui->lineEdit->setText("");// 把消息显⽰到界⾯上ui->listWidget->addItem(QString("客⼾端说: ") + text);// 发送消息给服务器socket->write(text.toUtf8());
}

修改widget.cpp中的Widget构造函数,通过信号槽,处理收到的服务器的响应

// 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {QString response = socket->readAll();qDebug() << response;ui->listWidget->addItem(QString("服务器说: ") + response);
});

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

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


HTTP Client

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

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

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

QNetworkAccessManager 提供了 HTTP 的核心操作

方法说明
get(constQNetworkRequest&)发起⼀个HTTPGET请求,返回QNetworkReply对象
post(constQNetworkRequest&,const QByteArray&)发起⼀个HTTPPOST请求,返回QNetworkReply对象

QNetworkRequest 表示一个HTTP请求(不含body)
若需要发送一个带有 body 的请求(如post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body

方法说明
QNetworkRequest(constQUrl&)通过URL构造⼀个HTTP请求

setHeader(QNetworkRequest::KnownHeaders header,constQVariant&value)

设置请求头

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

取值说明
ContentTypeHeader描述body的类型
ContentLengthHeader描述body的长度
LocationHeader⽤于重定向报文中指定重定向地址.(响应中使用,请求用不到)
CookieHeader设置cookie
UserAgentHeader设置User-Agent

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

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

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

代码示例:HTTP客户端

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

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

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

修改widget.h,创建QNetworkAccessManager属性

class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();private:Ui::Widget *ui;// 新增属性QNetworkAccessManager* manager;
};

修改widget.cpp,创建实例
 

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 实例化属性manager = new QNetworkAccessManager(this);
}

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

http://www.ppmy.cn/server/167248.html

相关文章

docker compose部署flink集群

本次部署2个jobmanager和3个taskmanager 一、部署zookeeper集群 flink使用zookeeper用作高可用 部署集群参考&#xff1a;docker compose部署zookeeper集群-CSDN博客 二、创建目录及配置文件 创建timezone文件&#xff0c;内容填写Asia/Shanghai 手动创建目录&#xff1a…

用php tp6对接钉钉审批流的 table 表格 明细控件 旧版sdk

核心代码 foreach ($flows[product_list] as $k>$gift) {$items_list[] [[name > 商品名称, value > $gift[product_name] ?? ],[name > 规格, value > $gift[product_name] ?? ],[name > 数量, value > $gift[quantity] ?? ],[name > 单位, v…

Linux——信号

前言&#xff1a;本文主要介绍信号是什么&#xff0c;信号的产生以及核心转储的机制。 一、信号 1&#xff09;信号是什么 信号&#xff0c;又称为软中断信号&#xff0c;是Linux系统响应某些条件而产生的一个事件。是操作系统向一个进程或者线程发送的一种异步通知&#xff0…

ROG NUC小而强的秘密,游戏体验MAX

作为ROG第一款迷你主机&#xff0c;ROG NUC以其强大的性能和出色的设计&#xff0c;成为众多游戏爱好者关注的焦点。我们来看看它究竟能否凭借小巧的机身&#xff0c;在游戏中展现惊人实力。 硬件在手&#xff0c;游戏跟我走 ROG NUC有2个配置版本可选&#xff0c;ROG NUC 760…

【深度学习入门_机器学习理论】决策树(Decision Tree)

本部分主要为机器学习理论入门_决策树算法&#xff0c;书籍参考 “ 统计学习方法&#xff08;第二版&#xff09;”。 学习目标&#xff1a; 熟悉决策树基础知识&#xff1a;树、熵、信息增益、基尼指数&#xff1b;熟悉决策树构建步骤&#xff1b;熟悉3种典型决策树算法&…

支持向量机(一)

支持向量机是典型的二分类模型&#xff0c;以其模型简单、实现简单、效果卓越而著称。 一元支持向量机 我们通过一条中间线根据特征对样本实现分类&#xff0c;很明显&#xff1a;两个支持样本的差别越大&#xff0c;两个支持样本的分类效果就越好。 二元支持向量机 在实际生…

ChunkKV:优化 KV 缓存压缩,让 LLM 长文本推理更高效

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

基于微信小程序的博物馆预约系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…