Qt——网络编程

news2025/3/31 9:18:44

和多线程类似, Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装。

在进行网络编程之前, 需要在项目中的 .pro 文件中添加 network 模块,添加之后运行一下,使其包含的头文件能够被加载进Qt Creator。

Qt本身是一个非常庞大,包罗万象的框架,如果直接进行编程,则会导致程序的内存过大,所以其包含的各种功能被分别存储在不同的模块,按需使用。


一.UDP

1.相关API

UDP核心的两个类为QUdpSocket QNetworkDatagram

QUdpSocket 表示⼀个 UDP 的 socket 文件,其包含的API如下:

bind(const QHostAddress&, quint16) :绑定指定的端口号

receiveDatagram():返回 QNetworkDatagram ,读取 一个 UDP 数据报

writeDatagram(const QNetworkDatagram&):发送一个 UDP 数据报

readyRead:是一个信号,在收到数据并准备就绪后触发. (类似于 IO 多路复用的通知机制)

 QNetworkDatagram 表示一个 UDP 数据报,其包含的API有:

QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16):

通过 QByteArray , 目标 IP 地址, 目标端口号构造一个 UDP 数据报. 通常用于发送数据时.

data():

获取数据报内部持有的数据。返回 QByteArray

senderAddress():

获取数据报中包含的对端的 IP 地址。

senderPort():

获取数据报中包含的对端的端口号。


2.回显服务器 

   //创建出对象
    socket = new QUdpSocket(this);
    //设置窗口标题
    this->setWindowTitle("服务器");
    //连接信号槽
    connect(socket,&QUdpSocket::readyRead,this,&Widget::processRequest);
    //绑定端口号
    bool ret = socket->bind(QHostAddress::Any,9090);
    if(!ret)
    {
        //绑定失败
        QMessageBox::critical(this,"服务器启动出错",socket->errorString());
        return;
    }

在构造函数中,需要完成套接字对象的创建,连接信号槽并绑定IP和端口号,IP设置为任意IP。 

随后在槽函数中执行服务器的核心逻辑:

//服务器的核心逻辑
void Widget::processRequest()
{
    //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);
}

//根据请求计算响应
QString Widget::process(const QString& request)
{
    return request;
}

将收到和发送的数据通过一个列表框显示。


 3.回显客户端

服务器和客户端需要分别构建成一个项目来实现。 

我们希望设计一个客户端界面,可以输入内容并发送,并会显示发送和收到的数据

按钮槽函数的设计如下: 

void Widget::on_pushButton_clicked()
{
    //1.获取输入框内容
    const QString& text = ui->lineEdit->text();
    //2.构建UDP请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT);
    //3.发送请求数据
    socket->writeDatagram(requestDatagram);
    //4.把发送的数据添加到列表框中
    ui->listWidget->addItem("客户端说:" + text);
    //5.清空输入框内容
    ui->lineEdit->setText("");
}

 输入内容,将内容数据提取,构建UDP请求报文并发送给服务器,随后将内容显示到列表框中。

//定义两个常量,分别代表服务器的地址和端口号
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

void Widget::process()
{
    //1.获取服务器响应
    QNetworkDatagram responseDategram = socket->receiveDatagram();
    //2.读取数据并显示到列表框中
    QString text = responseDategram.data();
    ui->listWidget->addItem("服务器说:" + text);
}

定义常量IP和端口号,代表服务器来进行连接,并构建信号槽函数,获取服务器的响应数据并显示到列表框中,结果如下: 


二.TCP

 1.相关API

TCP两个核心类为 QTcpServer QTcpSocket

QTcpServer 用于监听端口, 和获取客户端连接,相关API如下:

listen(const QHostAddress&, quint16 port) :绑定指定的地址和端口号,并开始监听。

nextPendingConnection() :从系统中获取到⼀个已经建立好的 tcp 连接。返回⼀个 QTcpSocket , 表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信。

newConnection :是一个信号,有新的客户端建立连接好之后触发。

QTcpSocket 用于客户端和服务器之间的数据交互,相关API如下:

readAll() :读取当前接收缓冲区中的所有数据。返回 QByteArray 对象。

write(const QByteArray&): 把数据写入 socket 中。

deleteLater() :暂时把 socket 对象标记为无效。Qt 会在下个事件循环中析构释放该对象。

readyRead :是一个信号,有数据到达并准备就绪时触发。

disconnected :是一个信号,连接断开时触发。

connectToHost(const QString& , quint16):和服务器建立连接。

waitForConnected():等待并确认连接是否出错。


 2.回显服务器 

在TCP服务器和客户端,我们同样设计一个跟上述UDP一致的界面。

    ui->setupUi(this);
    //1.修改窗口标题
    this->setWindowTitle("服务器");
    //2.创建TcpServer对象
    tcpserver = new QTcpServer(this);
    //3.通过信号槽,指定如何处理连接
    connect(tcpserver,&QTcpServer::newConnection,this,&Widget::ProcessConnection);
    //4.bind IP和端口号
    bool ret = tcpserver->listen(QHostAddress::Any,9090);
    if(!ret)
    {
        QMessageBox::critical(this,"服务器启动失败!",tcpserver->errorString());
        exit(1);
    }

TCP相较于UDP则更加复杂。 

在构造函数中同样需要创建对象,不同的是这里创建的不是套接字对象,而是服务器对象,TCP不同于UDP能够直接连接,必须通过监听来获取连接。所以需要构建信号槽来处理连接。

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, [=](){
        //a.读取请求数据
        QString request = clientSocket->readAll();
        //b.根据请求处理响应
        const QString& response = process(request);
        //c.把响应写回到客户端
        clientSocket->write(response.toUtf8());
        //d.把上述信息记录到日志
        QString log = "[" + clientSocket->peerAddress().toString() + ":" +
                QString::number(clientSocket->peerPort()) + "] " + "req: " +
                request + "resp: " + response;
        ui->listWidget->addItem(log);
    });
    
    //3.通过信号槽,处理客户端断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
        //a.显示断开连接的日志
        QString log = "[" + clientSocket->peerAddress().toString() + ":" +
                QString::number(clientSocket->peerPort()) + "] 客户端下线啦!";
        //b.使用deleteLater释放
        clientSocket->deleteLater();
    });
}
//处理回显服务器
QString Widget::process(const QString request)
{
    return request;
}

通过创建 QTcpSocket 对象来获取客户端报文,进而通过信号槽来处理报文和响应。这里的槽函数写成了lambda表达式

最后,由于 QTcpSocket 对象没有挂在 Qt 的对象树上所以当其断开连接时,必须通过信号槽将其手动释放


3.回显客户端

    //1.修改窗口标题
    this->setWindowTitle("客户端");
    //2.获取Socket对象
    socket = new QTcpSocket(this);
    //3.和服务器建立连接
    socket->connectToHost("127.0.0.1", 9090);
    //4.连接信号槽,处理服务器响应
    connect(socket, &QTcpSocket::readyRead, this, [=](){
        //a.读取响应数据
        QString response = socket->readAll();
        //b.将数据显示在列表框上
        ui->listWidget->addItem("服务器说:" + response);
    });
    //5.等待和确认连接是否建立成功
    if(!socket->waitForConnected())
    {
        QMessageBox::critical(this,"连接服务器出错", socket->errorString());
        exit(1);
    }

在构造函数中,和服务器建立连接,并构建信号槽处理服务器响应

void Widget::on_pushButton_clicked()
{
    //1.获取输入框信息
    QString text = ui->lineEdit->text();
    //2.将数据发送给服务器
    socket->write(text.toUtf8());
    //3.把发送的信息显示在列表框上
    ui->listWidget->addItem("客户端说:" + text);
    //4.清空输入框内容
    ui->lineEdit->setText("");
}

按钮槽函数的设计与UDP基本一致。

结果如下:


三.HTTP

在Qt中,只提供了HTTP的客户端库,未提供服务器库,因此,只能在Qt上开发HTTP客户端


1.相关API

进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议,其核心类包括:QNetworkAccessManager 、 QNetworkRequest 、 QNetworkReply 

QNetworkAccessManager 提供了 HTTP 的核心操作

get(const QNetworkRequest&)

发起⼀个 HTTP GET 请求。 返回 QNetworkReply 对象

post(const QNetworkRequest& , const QByteArray&)

发起⼀个 HTTP POST 请求。返回 QNetworkReply 对象

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

QNetworkRequest(const QUrl& ):

通过 URL 构造⼀个 HTTP 请求

setHeader(QNetworkRequest::KnownHeaders header, const QVariant &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 会在客户端收到完整的响应数据之后触发.


 2.客户端实现

设计HTTP客户端时,需要在构造函数中创建 QNetworkAccessManager 对象,随后在按钮槽函数中实现请求和响应处理

void Widget::on_pushButton_clicked()
{
    //1.从输入框获取hul
    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)
       {
           //获取到正确的HTTP响应
           QString html = response->readAll();
           ui->plainTextEdit->setPlainText(html);
       }
       else
       {
           //响应出错了
           ui->plainTextEdit->setPlainText(response->errorString());
       }
       //还需要对response释放
       response->deleteLater();
    });
}

 这里设计了一个简单的不带body的HTTP请求,发送之后通过信号槽获取响应并显示在文本框中。

结果如下: 


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker 安装 mysql 详解

在平常的开发工作中,我们经常需要用到 mysql 数据库。那么在docker容器中,应该怎么安装mysql数据库呢。简单来说,第一步:拉取镜像;第二步:创建挂载目录并设置 my.conf;第三步:启动容…

no persistent volumes available for this claim and no storage class is set

目录标题 问题1问题描述**问题原因****解决步骤****1. 检查 PVC 的配置****2. 设置 StorageClass****创建默认 StorageClass****修改 PVC 的 StorageClass** **3. 创建匹配的 PV****4. 验证 PVC 是否绑定成功** **最佳实践** 问题2问题描述**问题原因****解决方案****1. 删除现…

2024微短剧行业生态洞察报告汇总PDF洞察(附原数据表)

原文链接: https://tecdat.cn/?p39072 本报告合集洞察从多个维度全面解读微短剧行业。在行业发展层面,市场规模与用户规模双增长,创造大量高收入就业岗位并带动产业链升级。内容创作上,精品化、品牌化趋势凸显,题材走…

【22】Word:小李-高新技术企业政策❗

目录 题目​ NO1.2 NO3 NO4 NO5.6 NO7.8 NO9.10 若文章中存在删除空白行等要求,可以到最后来完成。注意最后一定要检查此部分!注意:大多是和事例一样即可,不用一摸一样,但也不要差太多。 题目 NO1.2 F12Fn&a…

SQLmap 自动注入 -02

1: 如果想获得SQL 数据库的信息,可以加入参数: -dbs sqlmap -u "http://192.168.56.133/mutillidae/index.php?pageuser-info.php&usernamexiaosheng&passwordabc&user-info-php-submit-buttonViewAccountDetails" --batch -p username -dbs…

视频m3u8形式播放 -- python and html

hls hls官网地址 创建项目 ts为视频片段 m3u8文件内容 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" …

C#中的语句

C#提供了各式各样的语句&#xff0c;大多数是由C和C发展而来&#xff0c;当然&#xff0c;在C#中做了相应修改。语句和表达式一样&#xff0c;都是C#程序的基本组成部分&#xff0c;在本文我们来一起学习C#语句。 1.语句 语句是构造所有C#程序的过程构造块。在语句中可以声明…

【2024年CSDN平台总结:新生与成长之路】

&#x1f4ab;引言 2024年已经过去&#xff0c;回顾这一年&#xff0c;所有的经历依然历历在目。以“经验”为动力&#xff0c;我正迈向2025年。回顾自己在CSDN平台上的创作之路&#xff0c;收获满满、成长颇多&#xff0c;也有许多宝贵的感悟。接下来&#xff0c;我将分享这一…

架构思考与实践:从通用到场景的转变

在当今复杂多变的商业环境中&#xff0c;企业架构的设计与优化成为了一个关键议题。本文通过一系列随笔&#xff0c;探讨了业务架构的价值、从通用架构到场景架构的转变、恰如其分的架构设计以及如何避免盲目低效等问题。通过对多个实际案例的分析&#xff0c;笔者揭示了架构设…

AG32 FPGA 的 Block RAM 资源:M9K 使用

1. 概述 AG32 FPGA 包含了 4 个 M9K 块&#xff0c;每个 M9K 块的容量为 8192 bits&#xff0c;总计为 4 个 M9K&#xff08;4K bytes&#xff09;。这使得 AG32 的内部存储非常适合嵌入式应用&#xff0c;能够有效地利用片上资源。 M9K 参数 参考自《AGRV2K_Rev2.0.pdf》。…

Java中如何安全地停止线程?

大家好&#xff0c;我是锋哥。今天分享关于【Java中如何安全地停止线程?】面试题。希望对大家有帮助&#xff1b; Java中如何安全地停止线程? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Java中&#xff0c;安全地停止线程是一项重要的任务&#xff0c;尤其…

2025寒假备战蓝桥杯01---朴素二分查找的学习

文章目录 1.暴力方法的引入2.暴力解法的思考 与改进3.朴素二分查找的引入4.朴素二分查找的流程5.朴素二分查找的细节6.朴素二分查找的题目 1.暴力方法的引入 对于下面的这个有序的数据元素的组合&#xff0c;我们的暴力解法就是挨个进行遍历操作&#xff0c;一直找到和我们的这…

【HF设计模式】06-命令模式

声明&#xff1a;仅为个人学习总结&#xff0c;还请批判性查看&#xff0c;如有不同观点&#xff0c;欢迎交流。 摘要 《Head First设计模式》第6章笔记&#xff1a;结合示例应用和代码&#xff0c;介绍命令模式&#xff0c;包括遇到的问题、采用的解决方案、遵循的 OO 原则、…

Ubuntu16.04 安装OpenCV4.5.4 避坑

Ubuntu16.04 安装C版OpenCV4.5.4 Ubuntu16.04 VSCode下cmakeclanglldb调试c 文章目录 Ubuntu16.04 安装C版OpenCV4.5.41. 下载Opencv压缩包2. 安装Opencv-4.5.43. 配置OpenCV的编译环境4.测试是否安装成功 1. 下载Opencv压缩包 下载Opencv压缩包&#xff0c;选择source版本。…

Flutter 改完安卓 applicationId 后App 闪退问题。

一、问题 当我们项目创建完&#xff0c;想 build.gradle 改 applicationId 的时候&#xff0c;再次执行的时候可能会出现 app 闪退问题&#xff0c; 控制台不显示任何错误提示 也不出现 Exit 停止运行的情况。&#xff08;像下方这样&#xff0c; 而 app 只是在模拟器中一闪而…

专利申请流程详解

专利申请流程详解 文章目录 专利申请流程详解前言一、什么是专利二、如何申请专利1. 注册国家知识产权局账号2. 账号登录和认证3. 专利费用减免4. 专利申请 三、注意事项&#xff08;一&#xff09;、外观设计专利&#xff08;二&#xff09;、实用新型专利&#xff08;三&…

使用缓存保存验证码进行登录校验

在Spring Boot项目中使用Redis进行登录校验&#xff0c;一般的做法是将用户的登录状态&#xff08;例如&#xff0c;JWT令牌或者用户信息&#xff09;存储在Redis中&#xff0c;并在后续请求中进行校验。 我们需要建立两个拦截器&#xff1a;RefreshTokenInterceptor LoginIn…

会议签到系统的架构和实现

会议签到系统的架构和实现 摘要:通过定制安卓会议机开机APP呈现签到界面&#xff0c;并且通过W/B结构采集管理签到信息&#xff0c;实现会议签到的功能。为达到此目标本文将探讨使用Redis提供后台数据支持&#xff1b;使用SocketIo处理适时消息&#xff1b;使用Flask进行原型开…

通过Ukey或者OTP动态口令实现windows安全登录

通过 安当SLA&#xff08;System Login Agent&#xff09;实现Windows安全登录认证&#xff0c;是一种基于双因素认证&#xff08;2FA&#xff09;的解决方案&#xff0c;旨在提升 Windows 系统的登录安全性。以下是详细的实现方法和步骤&#xff1a; 1. 安当SLA的核心功能 安…

.Net Core微服务入门系列(一)——项目搭建

系列文章目录 1、.Net Core微服务入门系列&#xff08;一&#xff09;——项目搭建 2、.Net Core微服务入门全纪录&#xff08;二&#xff09;——Consul-服务注册与发现&#xff08;上&#xff09; 3、.Net Core微服务入门全纪录&#xff08;三&#xff09;——Consul-服务注…