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

【Qt】网络编程

目录

一.UDP

1.1.QUdpSocket常用方法

1.2.QUdpSocket常用信号——readyRead

1.2.QNetworkDatagram常用方法

1.3.示例——UDP回显服务端的编写

1.4.示例——UDP回显客户端的编写

1.5.理解QString和QByteArray的相互转换

1.5.1.从 QString 转换为 QByteArray

1.5.2.从 QByteArray 转换为 QString

1.5.3.特殊情况与技巧

二.TCP

2.1.QTcpServer核心方法

2.2.QTcpServer核心信号

2.3.QTcpSocket核心方法

2.4.QTcpSocket核心信号

2.5.示例——TCP回显服务器

2.6.示例——TCP回显客户端

三.HTTP

3.1.QNetworkAccessManager

3.2.QNetworkRequest

3.3.QNetworkReply

3.4.核心信号——QNetworkReply::finished

3.4.简单示例——HTTP客户端


和多线程类似,Qt为了⽀持跨平台,对⽹络编程的API也进⾏了重新封装.

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

注意:

在进⾏⽹络编程之前,需要在项⽬中的.pro ⽂件中添加 network 模块.

加之后要⼿动编译⼀下项⽬,使QtCreator能够加载对应模块的头⽂件。

一.UDP

其实主要的类是QUdpSocket和QNetWorkDatagram

1.1.QUdpSocket常用方法

1. bind(const QHostAddress &address, quint16 port) - “占地盘”

作用:

这个方法的作用是让 QUdpSocket 对象“绑定”到一个本地的网络地址和端口号上。你可以把它想象成给你的应用程序分配一个专属的“邮箱”或者“门牌号”。

  • 地址(address):指定绑定到本机的哪个IP地址。比如 QHostAddress::Any (IPv4 的 0.0.0.0) 表示监听所有本机的网络接口(网卡)收到的数据。如果你的机器有多个网卡(比如有线、WiFi),用这个就能都监听到。你也可以指定一个具体的IP,如 QHostAddress(“192.168.1.100”),那么它就只监听发往这个IP的数据。

  • 端口(port):这是最关键的部分。UDP数据包是通过端口号来区分不同应用程序的。比如,DNS服务通常使用53端口。你的程序想接收UDP数据,就必须绑定到一个特定的端口上,告诉操作系统:“所有发往我这个端口的数据,都交给我来处理!”

对标原生API:
对标伯克利套接字(BSD Socket)的 bind() 函数。作用一模一样。

使用场景与细节:

  • 作为接收方:如果你想接收UDP数据报,必须先调用 bind。否则,你的 socket 没有“地盘”,操作系统不知道把数据交给谁。

  • 作为纯发送方:你也可以不调用 bind。当你第一次调用 writeDatagram 发送数据时,系统会自动为你分配一个随机的、可用的本地端口。但如果你希望对方固定地回包到某个端口,那么你发送前最好先 bind 一个固定端口。

  • 返回值:返回 bool 类型,成功为 true,失败为 false。失败的原因可能是端口已被其他程序占用,或者你没有权限绑定该端口(如小于1024的端口在Linux/Unix下需要root权限)。


2.errorString() - “它到底怎么了?问个明白!”

作用:

errorString() 是 QUdpSocket(以及所有继承自 QAbstractSocket 的类)的一个方法。它的唯一作用就是:当 socket 发生错误时,返回一个可读的、描述性的字符串,告诉你到底出了什么问题。

网络编程充满了不确定性,很多事情都可能出错:

  • 端口被占用

  • 网络连接断开

  • 目标主机不可达

  • 没有权限操作某个端口

  • 内存不足

当你的 bind() 返回 false,或者 writeDatagram() 返回 -1 时,你只知道 “操作失败了”,但你不知道 “为什么会失败”errorString() 就是用来解答“为什么”这个关键问题的。

3. receiveDatagram() - “从邮箱取信”

作用:
这个方法用于从之前 bind 的“邮箱”(socket缓冲区)中读取一个等待处理的UDP数据报。它不接收任何参数,并返回一个 QNetworkDatagram 对象。

对标原生API:
对标原生的 recvfrom() 函数。但 QUdpSocket 的封装更友好、更现代。

返回的 QNetworkDatagram 对象包含什么?
这是关键!调用这个方法后,你得到的不是一个简单的 QByteArray,而是一个信息丰富的“数据报对象”,它包含:

  • 数据本身(Data):通过 datagram.data() 可以拿到数据内容的 QByteArray

  • 发送方地址(Sender Address):通过 datagram.senderAddress() 和 datagram.senderPort() 可以知道这个数据报是发过来的。这对于需要回复消息的场景至关重要。

  • 接收方地址(Receiver Address):通过 datagram.destinationAddress() 和 datagram.destinationPort() 可以知道这个数据报是发到本机的哪个IP和端口的。这在服务器有多个IP时有用。

  • 生存时间(TTL):在某些类型的socket上可以获取。

重要特性:

  • 阻塞与非阻塞:如果使用了 Qt::Blocking 模式,当没有数据时,调用此方法会一直等待,直到有数据到来。但在默认的 Qt::NonBlocking 模式下,如果缓冲区没有数据,它会立即返回一个无效的 QNetworkDatagram 对象(你可以通过 if (datagram.isValid()) {...} 来判断)。

  • 读取并移除:一旦你调用 receiveDatagram() 成功读取了一个数据报,这个数据报就会从socket的接收缓冲区中被移除。


4. writeDatagram(const QNetworkDatagram &datagram) - “寄信”

作用:
这个方法用于发送一个UDP数据报到网络中的另一台机器。

对标原生API:
对标原生的 sendto() 函数。

参数 QNetworkDatagram

你需要在调用前构造好这个“数据报对象”。

使用场景与细节:

  • 无需连接:UDP是无连接的,所以你每次发送都需要明确指定目标的地址和端口。

  • 发送到广播/组播:你可以将目标地址设置为广播地址(如 QHostAddress(“255.255.255.255”))或组播地址,来实现一对多的通信。

  • 返回值:返回 qint64 类型,表示成功发送的字节数。如果发送失败(如网络不可达),会返回 -1

  • 注意:UDP不保证数据一定能送达,也不保证数据包的顺序。这是由UDP协议本身的特性决定的。


1.2.QUdpSocket常用信号——readyRead

readyRead 信号 - “邮箱有新信了,快来取!”

作用:

这是一个信号,而不是一个方法。它是 QUdpSocket 事件驱动编程的核心。

对标原生机制:

对标IO多路复用机制(如 selectpollepoll)的通知功能。当socket变得“可读”时,这些机制会通知你,readyRead 信号做了同样的事情,但更符合Qt的风格。

它是如何工作的?

在Qt的事件循环(Event Loop)中,QUdpSocket 会一直监听底层的socket。一旦操作系统通知它有新的UDP数据报到达,并且已经被存入接收缓冲区,QUdpSocket 对象就会立刻发射(emit) 这个 readyRead 信号。

你该怎么做?
你需要将你的一个槽函数(Slot) 连接到这个信号上。

为什么它如此重要?
因为它实现了异步通知。你不需要自己写一个循环不停地去调用 receiveDatagram()(这叫“轮询”,非常浪费CPU)。你只需要“告诉”Qt:“当有数据时,请调用我的 handlePendingDatagrams 函数”。这样,你的程序在等待数据时可以去做别的事情,CPU利用率高,响应也及时。

总结与工作流程

一个典型的UDP接收端程序流程是这样的:

  1. 创建SocketQUdpSocket udpSocket;

  2. 绑定端口udpSocket.bind(QHostAddress::Any, 7755);

  3. 连接信号connect(&udpSocket, &QUdpSocket::readyRead, this, &MyClass::readData);

  4. 事件循环:进入Qt的事件循环(app.exec())。

  5. 触发与处理

    • 当有数据到达端口7755时,操作系统通知Qt。

    • Qt使 udpSocket 发射 readyRead 信号。

    • 你的 readData 槽函数被自动调用。

    • 在 readData 里,你调用 receiveDatagram() 来取出并处理数据。

1.2.QNetworkDatagram常用方法

什么是 QNetworkDatagram?

您可以把它理解成一个 “UDP 数据包”的包装器。在网络编程中,UDP 是一种无连接的、不可靠的、但效率很高的传输协议。一个 UDP 数据报就是一个完整的数据包,它包含了您要发送的数据本身,以及目标地址信息(当您发送时)或来源地址信息(当您接收时)。

QNetworkDatagram对象就封装了这三样东西:

  1. 数据内容

  2. IP 地址

  3. 端口号


下面我就讲讲这个类里面最重要的几个函数

1. 构造函数:QNetworkDatagram(const QByteArray& data, const QHostAddress& destinationAddress, quint16 port)

  • 功能说明
    这个构造函数用于创建一个准备发送的 UDP 数据报

    • data: 这是您想要通过 UDP 发送的实际内容,比如一段文字、一串序列化后的数字等等。它被包装在 QByteArray 中。

    • destinationAddress: 这是数据报要发送到的目标主机的 IP 地址,例如 QHostAddress("192.168.1.100") 或 QHostAddress::Broadcast(用于广播)。

    • port: 这是目标主机上正在监听的那个应用程序的端口号。例如,如果目标程序在 8888 端口等待数据,这里就填 8888。

  • 使用场景当您使用 QUdpSocket 发送数据时,您不会直接调用 QUdpSocket 的 write 方法并附带地址,而是先创建一个 QNetworkDatagram 对象,然后通过 QUdpSocket::writeDatagram 将这个数据报对象发送出去。

  • 代码示例

    // 假设我们要发送字符串 "Hello UDP!" 到本机(127.0.0.1)的 12345 端口
    QByteArray dataToSend = "Hello UDP!";
    QHostAddress targetAddress = QHostAddress::LocalHost; // 即 127.0.0.1
    quint16 targetPort = 12345;// 使用构造函数创建数据报
    QNetworkDatagram datagramToSend(dataToSend, targetAddress, targetPort);// 然后通过一个已创建的 udpSocket 发送它
    QUdpSocket udpSocket;
    udpSocket.writeDatagram(datagramToSend);


2. data() 方法

  • 功能说明
    这个方法用于从数据报中提取出有效载荷,也就是实际传输的数据内容。它返回一个 QByteArray

  • 使用场景这个方法在接收 UDP 数据报时极其常用。当您的 QUdpSocket 收到一个数据报后,您会得到一个 QNetworkDatagram 对象,然后必须调用它的 data() 方法来读取里面的内容。

  • 这个返回的是一个QByteArray,可以赋值给QString。


3. senderAddress() 方法

  • 功能说明
    这个方法返回一个 QHostAddress 对象,它是发送方的IP地址,也就是这数据是谁发来的,它代表了发送这个数据报的远端机器的 IP 地址

  • 使用场景:这个方法仅在接收到的数据报上有意义。当您的程序从一个 UDP 端口收到数据时,您不仅关心数据内容,通常还关心“这数据是谁发来的?”,以便进行回复或记录日志。senderAddress() 就提供了这个“谁”的 IP 地址信息。


4. senderPort() 方法

  • 功能说明
    这个方法返回一个 quint16(即 16 位无符号整数),它代表了发送这个数据报的远端应用程序所使用的端口号

  • 使用场景:与 senderAddress() 完全一样,它也是仅在接收数据报时使用。IP 地址定位到主机,而端口号定位到主机上的具体应用程序。知道了 IP 和端口,您就能完整地标识出发送方,并可以向它回复消息。

1.3.示例——UDP回显服务端的编写

我们创建一个项目(基于QWidget)

注意事项1

我们这里的bind里面第一个参数是需要绑定的IP地址,我们在上面使用了QHostAddress::Any,那其实是一个枚举值

我们来看一下这个枚举类型,它定义了QHostAddress的一些特殊地址。这些地址在网络编程中非常常见,它们各自有特定的含义。下面我们逐一解释:

  1. Null:表示一个空地址。它不指向任何地址,类似于一个未初始化的地址对象。

  2. Broadcast:广播地址。用于在本地网络中向所有设备发送数据。在IPv4中,通常指的是255.255.255.255。当向这个地址发送数据时,同一网络段内的所有主机都会收到。

  3. LocalHost:本地回环地址(IPv4)。通常指的是127.0.0.1,用于本地机器上的进程间通信。发送到这个地址的数据不会进入网络,而是直接在本地处理。

  4. LocalHostIPv6:IPv6的本地回环地址。指的是::1,功能与IPv4的127.0.0.1相同。

  5. Any:任意地址。在IPv4中,通常表示为0.0.0.0。当服务器监听这个地址时,表示它愿意接受来自任何网络接口的连接。在Qt中,这个值在IPv4和IPv6中都有定义,但根据上下文可能指向IPv4或IPv6。但是注意,这里有一个AnyIPv6和AnyIPv4,所以Any可能是为了兼容性,具体取决于配置。

  6. AnyIPv6:IPv6的任意地址。指的是::(全零地址)。当服务器监听这个地址时,它会接受所有IPv6接口上的连接。

  7. AnyIPv4:IPv4的任意地址。指的是0.0.0.0,表示服务器会接受所有IPv4接口上的连接。

注意事项2

为什么需要调用这个函数?

因为这QNetWorkDatagram的构造函数第一个参数是QByteArray类型的,而我们这个response则是QString类型的。它们的类型不同,需要另外转换

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

例如:

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

1.4.示例——UDP回显客户端的编写

当然,有了服务端还不行,我们还需要一个客户端。

我们创建一个新的项目

注意了啊:不要忘记了添加下面这个啊

还是很不错的啊。

好了,现在可以来编写代码了

好了我们现在就算是完成了。

我们运行一下,注意我们先运行服务端,再运行客户端

我们点击发送

完全没有一点问题啊!!

其实我们也可以启动多个客户端程序来连接这么一个服务端

我们直接双击运行这个.exe文件就能打开另外一个客户端。

问题1

在学习 Linux 网络编程时,我们曾经使用云服务器部署过服务器程序,当时其他同学的客户端也能够顺利连接上来。

那么现在的问题是:我们能否将目前用 Qt 编写的 UDP 服务器程序也部署到云服务器上呢?

大概率是不行的——这主要取决于你的云服务器是否安装了图形化界面。因为 Qt 程序通常需要依赖图形界面环境才能运行,而我们之前使用的 Linux 云服务器一般是纯命令行环境,并没有预装图形界面。如果确实需要运行 Qt 程序,就需要手动安装图形化界面及相关依赖。

问题2

另一个问题是:我们能否用现在用QT编写的 UDP 客户端,连接到之前 Linux 阶段写的 UDP 服务器呢?

这一点是完全可行的 ✅ 这也正体现了网络编程和协议标准化的重要意义——只要通信协议一致,不同平台、不同技术实现的客户端和服务器之间完全可以互相通信。

在实际商业项目中,服务器程序通常不会使用 Qt 编写,而更可能采用 C/C++、Go、Java 等技术栈,以保证在高并发场景下的性能和稳定性。而客户端方面,Qt 凭借其良好的跨平台特性和丰富的界面组件,常被选作桌面客户端开发框架。因此,使用 Qt 编写客户端连接非 Qt 实现的服务器,是非常常见且合理的架构选择。

1.5.理解QString和QByteArray的相互转换

理解它们的本质是成功转换的关键:

  • QString:Qt 中的字符串类。它存储的是 Unicode 字符(16位 QChar)。用于显示给用户看的文本。

  • QByteArray:Qt 中的字节数组类。它存储的是原始的 char 数据(8位字节)。用于处理二进制数据,或者没有编码信息的纯文本数据。

因为它们一个面向“字符”,一个面向“字节”,所以转换的桥梁就是编码


1.5.1.从 QString 转换为 QByteArray

当你有一个 QString,想把它变成字节流(例如,要保存到文件、通过网络发送,或调用一个需要 const char* 的 C 函数),你需要编码它。

最常用的方法是使用 QString 的成员函数。

使用 toUtf8()(最常用、推荐)

UTF-8 是一种兼容 ASCII 的 Unicode 编码,它是网络传输和文件存储的事实标准

QString str = "你好,世界!Hello World!";
QByteArray byteArray = str.toUtf8();// 此时 byteArray 里存储的就是字符串的 UTF-8 编码字节。
// 例如,"你" 字在 UTF-8 下是 3 个字节:0xE4 0xBD 0xA0

适用场景:几乎所有情况,尤其是在不同系统间交换数据、与 Web 服务通信、保存文件(如 JSON、XML)时。


1.5.2.从 QByteArray 转换为 QString

当你有一串字节(例如,从文件读取、从网络接收,或从 C 函数返回),并且你知道这串字节代表的是文本时,你需要解码它才能得到 QString

最常用的方法是使用 QString 的静态函数。

使用 fromUtf8()(最常用、推荐)

假设你的 QByteArray 中的数据是 UTF-8 编码的。

// 假设 byteArray 里存储的是 "你好" 的 UTF-8 字节
QByteArray byteArray = ...;
QString str = QString::fromUtf8(byteArray);

适用场景:读取 UTF-8 编码的文件、处理网络请求的 UTF-8 响应等。


1.5.3.特殊情况与技巧

1. 直接构造(隐式转换)

QString 的构造函数可以直接接受 QByteArray,但其行为等同于 fromUtf8()。为了代码清晰,建议显式使用 fromUtf8()。

QByteArray byteArray = "Hello";
QString str(byteArray); // 等价于 QString::fromUtf8(byteArray)

2. 与 const char* 的互操作

QByteArray 可以很方便地与 const char* 相互转换,这使其成为 QString 与 C 风格字符串之间的完美桥梁。

  • QString -> const char*:

    QString str = "Hello";
    QByteArray ba = str.toUtf8();
    const char *c_str = ba.constData(); // 或者 ba.data()
  • QString -> const char*:

    const char *c_str = "Hello";
    QString str = QString::fromUtf8(c_str);

二.TCP

核⼼类是两个: QTcpServer 和 QTcpSocket

QTcpServer 主要用于实现TCP服务器端的功能,负责监听指定端口,接受客户端的连接请求,并在新连接建立时创建一个QTcpSocket对象用于后续通信。

QTcpSocket 则代表一个TCP连接,用于客户端与服务器之间的实时数据读写和交互。它封装了TCP协议的核心操作,包括建立连接、传输数据以及连接状态管理,支持异步通信机制,能够高效处理网络数据的发送与接收。

总的来说,QTcpServer 负责接收连接,QTcpSocket 负责连接建立后的数据通信,二者共同构成了Qt网络模块中TCP通信的基础。

2.1.QTcpServer核心方法

QTcpServer ⽤于监听端⼝,和获取客⼾端连接.

1. listen(const QHostAddress &address, quint16 port) 方法

  • 文字说明
    这个方法是启动服务器的“总开关”。它的作用就是让服务器“坐下来,竖起耳朵”,在一个指定的网络地址和端口号上开始等待客户端的连接请求。

    • 绑定:首先,它会将服务器程序与您指定的 address (IP地址,如 QHostAddress::Any 表示所有本机IP) 和 port (端口号) 进行绑定。这就像是给服务器一个固定的“门牌号”,客户端才知道要连接哪里。

    • 监听:绑定成功后,它立即开始监听这个“门牌号”。这意味着操作系统会被告知:凡是发送到这个IP和端口的数据包,都请交给这个服务器程序处理。此时,服务器就进入了等待状态。

  • 返回值
    它是一个 bool 类型。如果监听成功(例如,端口没有被其他程序占用),返回 true;如果失败(例如,端口已被占用或权限不足),返回 false在调用后一定要检查返回值,这是编程的好习惯。

  • 对标原生API
    它实际上封装了原生 Berkeley Sockets API 中的 bind() 和 listen() 两个步骤。在原生C/C++中,你需要先调用 bind() 来绑定地址,再调用 listen() 来开始监听。Qt 将其简化为一个方法。

2. nextPendingConnection() 方法

  • 文字说明
    这个方法是你在收到“有新客户”的通知后,用来“接待”客户的具体动作。它从服务器的等待连接队列中,取出下一个已经建立好的连接,并将其包装成一个 QTcpSocket 对象返回给你。

    • 返回值:返回一个指向 QTcpSocket 对象的指针。这个 QTcpSocket 对象就代表了与那个特定客户端的通信通道。你之后所有与该客户端的读写操作,都通过这个 QTcpSocket 对象来完成。

    • 所有权:返回的 QTcpSocket 对象是没有父对象的,你需要负责管理它的生命周期。通常的做法是,在接收到这个对象后,立刻将其设置一个父对象,或者将其存储起来以便后续使用和销毁。

  • 调用时机
    这个方法必须在 newConnection 信号触发的槽函数内部(或由该槽函数引发的后续调用链中)调用。因为只有在这个时候,你才知道有新的连接在等待处理。

  • 对标原生API
    它直接对标原生API中的 accept() 函数。accept() 也是从监听套接字中接受一个新连接,并返回一个用于通信的新套接字描述符。

2.2.QTcpServer核心信号

newConnection 信号

  • 文字说明
    这是一个非常关键的信号。它不是你需要主动调用的方法,而是由 QTcpServer 在特定事件发生时自动发出的“广播”或“通知”。

    • 触发时机当有一个全新的客户端成功地通过三次握手与服务器建立了TCP连接时,这个信号就会被 QTcpServer 触发。

    • 作用:它告诉你:“喂!注意了,有个新客户来了,快去接待一下!” 在你的代码中,你需要将一个槽函数连接到这个信号。一旦信号发出,你的槽函数就会被自动调用,这样你就能在里面处理新的客户端连接了。

  • 工作模式类比(IO多路复用)
    你提到的“类似于IO多路复用中的通知机制”非常准确!

    • 在传统的阻塞式编程中,你可能会在一个循环里调用 accept(),如果没有新连接,程序就会卡在那里(阻塞)。

    • 而 QTcpServer 配合 newConnection 信号,是 事件驱动 的。你不需要主动去轮询询问“有新连接吗?”。你只需要告诉Qt:“当有新连接时,请调用我这个函数”。这背后的机制正是由Qt的事件循环实现的,它底层会使用像 selectpoll, 或 epoll 这样的IO多路复用技术来高效地监视所有网络连接。当有事件(如新连接)就绪时,事件循环会收到通知,并触发对应的信号。

    所以,newConnection 信号就是Qt封装好的、基于事件循环的“新连接通知”

2.3.QTcpSocket核心方法

QTcpSocket ⽤⼾客⼾端和服务器之间的数据交互

1. QByteArray readAll()

  • 说明:这是最重要的数据读取方法。它的作用是一次性读取当前接收缓冲区中积攒的所有数据,并将其作为一个整体返回。

  • 工作方式

    • Socket(套接字)在收到数据后,并不会立刻通知你,而是先将数据存入一个内部的“接收缓冲区”。

    • 当这个缓冲区里有新的数据到达时,readyRead 信号就会被触发。

    • 此时,你调用 readAll() 方法,就会把到目前为止缓冲区里所有的数据“一扫而空”,全部取出来。

  • 返回一个 QByteArray 对象。这是Qt中用于存放原始字节(包括文本和二进制数据)的容器。如果你知道数据是文本,可以再用QString::fromUtf8()等方法将其转换为QString。

  • 对标原生API:类似于 Berkeley Sockets (C语言)中的 read() 或 recv() 函数。

  • 重要注意事项

    • 清空缓冲区:调用 readAll() 后,内部缓冲区会被清空。再次调用会返回空数据,直到有新的数据到来。

    • 数据量:如果对端一次性发送了大量数据,readAll() 会返回所有数据。你需要确保你的程序有能力处理可能很大的数据块。

2. write(const QByteArray& data)

  • 说明:这是主要的写入方法。用于将一段数据(data)发送到连接的对方。

  • 工作方式

    • 你提供一个 QByteArray 作为参数,比如 socket->write(“Hello World”)

    • 这个方法会将这些数据放入一个内部的“发送缓冲区”,然后立即返回。数据的实际发送是异步的、在后台进行的

    • 这意味着 write() 方法本身不保证数据已经通过网络发送出去,它只负责将数据排队。这种非阻塞的特性是高性能事件驱动编程的核心。

  • 返回:通常会返回一个 qint64 值,表示成功排队到发送缓冲区的字节数。如果返回-1,则表示发生了错误。

  • 对标原生API:类似于 Berkeley Sockets 中的 write() 或 send() 函数。

  • 重要注意事项

    • 异步发送:你不能假设调用 write() 后对方就立刻收到了数据。

    • 缓冲:如果网络拥堵或对方接收慢,数据可能会在发送缓冲区中积压。

3. deleteLater()

  • 说明:这是一个用于安全销毁对象的方法。

  • 工作方式

    • 在事件驱动编程中,直接使用 delete 操作符删除一个正在处理事件的对象(比如Socket)是极其危险的,可能会导致程序崩溃(例如,一个事件还在处理中,但对象已经被删除了)。

    • deleteLater() 不会立即删除对象,而是给对象打上一个标记,告诉事件循环系统:“在当前事件处理完成,并返回到事件循环之后,再安全地删除这个对象”。

  • 它提供了一种安全、自动的延迟销毁机制。

  • 使用场景:通常在连接断开(例如在 disconnected 信号的槽函数中)后,需要销毁Socket对象时调用。socket->deleteLater();

2.4.QTcpSocket核心信号

1. readyRead()

  • 说明:这是最重要的数据到达通知信号。

  • 触发时机:当有新的数据从网络或对方进程到达,并被加载到Socket的接收缓冲区,准备就绪让你读取时,这个信号就会发射。

  • 工作方式

    • 你需要将一个槽函数(比如 onReadyRead())连接到这个信号。

    • 一旦信号发射,你的槽函数就会被调用,在槽函数里你就可以放心地使用 readAll() 或其他读取方法来获取数据。

  • 对标原生API:类似于 I/O 多路复用中的通知机制。在像 epoll 或 select 这样的原生API中,你会被通知某个文件描述符“可读了”,然后你再去读。readyRead 信号就是这种通知机制的面向对象封装。

  • 重要注意事项

    • 它只通知你“有数据可读”,但不告诉你具体有多少数据。你可能需要一次读取完,或者根据自定义的协议(如数据包头部声明长度)来分多次读取。

2. disconnected()

  • 说明:这是连接断开通知信号。

  • 触发时机:当对方主动关闭了连接,或者网络异常导致连接中断时,这个信号就会发射。

  • 工作方式

    • 你将一个槽函数(比如 onDisconnected())连接到这个信号。

    • 当连接断开时,槽函数被调用,你可以在里面进行清理工作,例如:释放资源、更新用户界面状态(显示“已断开”)、尝试重新连接等。

    • 通常也会在这个槽函数中调用 deleteLater() 来安全销毁Socket对象。

  • 对标原生API:在原生Socket编程中,你需要通过检查 recv() 的返回值(返回0)或 select/epoll 的错误事件来感知连接断开。disconnected 信号将这些底层细节封装了起来。

2.5.示例——TCP回显服务器

首先,大家千万不要忘了下面这个

接下来我们就来编写代码

2.6.示例——TCP回显客户端

我们创建一个新的项目

大家千万不要忘了

还是很不错的啊。

补充知识:QTcpSocket::connectToHost

这个函数就是客户端用来和服务端进行3次握手,然后建立连接的!!!

QTcpSocket::connectToHost 最核心的特性就是:它是一个非阻塞的、异步的函数。

这意味着:

  • 调用立即返回:当你调用这个函数时,它不会等待到目标服务器的网络连接完全建立好(即三次握手完成)才返回。它会几乎立刻将控制权交还给你的程序,让你的代码可以继续执行下去。
  • 后台操作:实际的连接建立过程(包括你提到的“三次握手”)是在系统后台由操作系统内核和Qt的事件循环来处理的。

既然函数不阻塞,我们如何知道连接是成功还是失败呢?答案就是Qt的信号与槽机制。

在调用 connectToHost 之后,在连接建立过程中的不同阶段,会触发不同的信号:

  • connected(): 这是最重要的信号! 当内核完成三次握手,连接成功建立时,Qt会发出这个信号。这是你开始发送数据的“发令枪”。
  • errorOccurred(QAbstractSocket::SocketError):如果连接失败(例如:服务器拒绝、网络超时、主机找不到等),会发出这个信号。

补充知识:QTcpSocket::waitForConnected

首先,waitForConnected是QTcpSocket的一个成员函数,它用于在调用connectToHost之后,阻塞等待直到连接建立(即三次握手完成)或者超时。

与connectToHost的非阻塞特性不同,waitForConnected是一个阻塞函数。这意味着调用waitForConnected后,当前线程会暂停执行,直到连接成功建立、发生错误或等待时间超过指定的超时时间。

函数原型如下:

bool QTcpSocket::waitForConnected(int msecs = 30000)

参数msecs是超时时间,单位是毫秒,默认值是30000毫秒(即30秒)。返回值是一个布尔值:

  • 如果连接在超时时间内成功建立,返回true;
  • 如果连接失败或超时,返回false。

下面详细描述其行为:

  • 如果socket已经处于连接状态,那么waitForConnected会立即返回true。

  • 如果socket还没有开始连接,即还没有调用connectToHost,那么waitForConnected将立即返回false。

  • 在调用了connectToHost之后,waitForConnected会阻塞,直到以下情况之一发生:
    a) 连接成功建立,返回true;
    b) 连接失败(例如,被拒绝或超时),返回false;
    c) 等待时间超过了msecs指定的毫秒数,返回false。


接下来我们运行一下

注意:需要先运行服务器,再运行客户端。

我们发现一运行,就出现了下面这个

点击发送

没有任何问题的!!!

我们让客户端下线的话

咋样!!!还是可以的吧。

三.HTTP

HTTP 协议在应用层上比底层的 TCP 或 UDP 更为抽象,也更贴近实际业务场景。

在 Qt 框架中,提供了用于实现 HTTP 客户端的相关类,方便开发者进行网络请求与资源访问。

需要注意的是,HTTP 协议本质上仍是基于 TCP 协议构建的,因此无论是实现一个 HTTP 客户端还是服务器,其核心都是对 TCP Socket 进行封装并在此基础上添加 HTTP 规范所需的逻辑处理。

目前,Qt 主要封装了 HTTP 客户端的实现,但并未提供官方的 HTTP 服务器库,若需实现服务端功能,开发者通常需要基于 Qt 的 TCP 服务器类(如 QTcpServer)自行构建符合 HTTP 标准的处理机制。


在 Qt 网络模块中,三个核心类构成了 HTTP 通信的基础,分别是:

  • QNetworkAccessManager
    它是整个 HTTP 操作的中枢,负责协调所有的网络请求与响应。通过它,我们可以发送请求、处理回复,并管理诸如 Cookie、缓存等网络相关功能。

  • QNetworkRequest
    该类用于封装一个 HTTP 请求的元信息,例如 URL、请求头、协议参数等。需要注意的是,它并不包含请求体(body)部分,仅用于描述请求的基本属性。

  • QNetworkReply
    该类代表服务器对HTTP 请求的响应。除了包含响应的状态码、响应头等元信息之外,它还继承自 QIODevice,因此可以像一般的 I/O 设备一样进行数据读取,尤其适合处理异步接收的响应内容。

通过这三个类的配合,Qt 为开发者提供了一套完整且易于使用的 HTTP 客户端编程接口。

3.1.QNetworkAccessManager

QNetworkAccessManager 提供了HTTP的核⼼操作

1. get(const QNetworkRequest &request)

  • 功能描述:此方法用于发起一个异步的 HTTP GET 请求。GET 方法通常用于向指定的服务器资源请求数据,请求的参数一般会附加在 URL 之后。

  • 参数说明它接收一个 QNetworkRequest 对象作为参数。这个对象包含了此次请求的所有必要信息,例如目标 URL、请求头以及各种属性设置。

  • 返回值与机制方法会立即返回一个 QNetworkReply 对象。这个对象代表着即将到来的网络响应。由于网络操作是异步的,调用此方法后,函数会立刻返回而不会阻塞当前线程。您需要通过连接 QNetworkReply 提供的信号(如 finished()readyRead()errorOccurred())来监听请求的完成状态、读取返回的数据或处理可能发生的错误。

  • 核心用途:主要用于从服务器获取信息,例如加载网页内容、下载文件、调用获取数据的 API 接口等。

2. post(const QNetworkRequest &request, const QByteArray &data)

  • 功能描述:此方法用于发起一个异步的 HTTP POST 请求。POST 方法通常用于向服务器提交数据,例如提交表单信息、上传文件或调用修改数据的 API 接口。

  • 参数说明

    • const QNetworkRequest &request:它接收一个 QNetworkRequest 对象作为参数。与 get 方法类似,此参数定义了请求的目标 URL、请求头等信息。

    • const QByteArray &data:此参数包含了要提交给服务器的请求体数据。您需要将数据转换为 QByteArray 格式。

  • 返回值与机制get 方法一样,它也会立即返回一个 QNetworkReply 对象。您同样需要通过连接该对象的信号来异步地处理服务器的响应。

  • 核心用途:主要用于向服务器发送数据,例如用户登录、发表评论、上传文件等操作。

总结与

这两个方法是 QNetworkAccessManager 最核心的接口,它们共同的特点是:

  • 异步非阻塞:调用后立即返回,不会阻塞程序执行。

  • 返回 QNetworkReply:通过这个对象来关联请求、跟踪状态并最终获取响应数据。

  • 信号与槽机制:整个请求和响应的生命周期都通过 Qt 的信号与槽机制进行管理和处理,这是 Qt 网络编程的典型风格。

3.2.QNetworkRequest

1. QNetworkRequest(const QUrl &url)

  • 功能描述:这是 QNetworkRequest 类最常用的构造函数,用于快速创建一个网络请求对象。

  • 参数说明:它接收一个 QUrl 类型的对象作为参数。这个 QUrl 对象指定了网络请求的目标地址,例如 "https:/baidu.com"。

  • 核心用途:它为整个 HTTP 请求奠定了基础,所有后续的请求头设置、属性配置都是围绕这个初始 URL 进行的。任何一个有效的网络请求都必须从一个明确的 URL 开始。

2. setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)

  • 功能描述:此方法用于为当前网络请求设置一个特定的、预定义的(已知的)HTTP 头部字段。它提供了一种类型安全且易于记忆的方式来设置常用请求头。

  • 参数说明

    • header:这是一个QNetworkRequest::KnownHeaders枚举类型的值。它指定了你要设置的是哪一个标准的 HTTP 头部字段(例如内容类型、用户代理等)。

    • value:这是一个QVariant类型的值,这个类型其实是一个类型可变的值,什么都能传,比如说字符串,数字啥的都能传进去用于提供对应头部字段的具体内容。你需要根据不同的 header 类型传入相应格式的值。

  • 核心用途:精确地控制 HTTP 请求的元数据,与服务器进行正确的通信。例如,告诉服务器你发送的数据是什么格式,或者模拟特定浏览器的请求。

常用 QNetworkRequest::KnownHeaders 枚举值详解

QNetworkRequest::ContentTypeHeader

  • 功能描述:用于描述请求体的媒体类型。

  • 使用场景:主要在发送 POSTPUT 等带有请求体的请求时使用。它告知服务器应该如何解析请求体中包含的数据。

  • 典型取值

    • "application/x-www-form-urlencoded":用于提交普通的网页表单。

    • "application/json":用于告知服务器请求体是 JSON 格式的数据。

    • "multipart/form-data":用于上传文件。

QNetworkRequest::ContentLengthHeader

  • 功能描述:用于明确指定请求体的字节长度。

  • 使用场景:在发送 POST 等请求时,有时需要显式地设置内容长度。不过,在大多数情况下,当使用 QNetworkAccessManager::post 方法并传入 QByteArray 作为数据体时,Qt 网络框架会自动计算并设置此头部,无需手动干预。

QNetworkRequest::UserAgentHeader

  • 功能描述:用于标识发出请求的客户端软件(如浏览器、应用程序)的类型、版本和操作系统等信息。

  • 使用场景:几乎所有的 HTTP 请求都会设置此头部。服务器常根据此信息来为不同类型的客户端返回优化后的内容(例如,为移动端和桌面端返回不同的页面)。设置一个恰当的 User-Agent 是确保与服务器正常交互的重要一环。

QNetworkRequest::LocationHeader

  • 功能描述:用于响应报文中,指示重定向的目标 URL。

  • 使用场景请注意,这个头部通常在服务器返回的响应(QNetworkReply)中读取,而不是在请求(QNetworkRequest)中设置。 当服务器返回 3xx 重定向状态码时,此头部会包含客户端应该自动跳转的新地址。

QNetworkRequest::CookieHeader

  • 功能描述:用于在请求中向服务器发送之前存储的 Cookie 信息。

  • 使用场景:当需要手动管理或设置特定的 Cookie 时使用。不过,更常见的做法是使用 QNetworkAccessManager 内建的 QNetworkCookieJar 来自动管理 Cookie 的存储与发送,这样可以避免手动设置此头部。

3.3.QNetworkReply

1. 方法:error()

  • 文字描述:这个方法用于获取网络请求过程中发生的错误类型。它返回一个 QNetworkReply::NetworkError 枚举值。

  • 如果请求成功完成,没有发生任何错误,它的返回值将是 QNetworkReply::NoError

  • 如果请求失败了(例如,服务器返回 404,连接超时,连接被拒绝等),这个方法就会返回一个特定的错误代码,帮助你精确地定位问题所在。

  • 使用场景:通常在一个请求完成(例如在 finished() 信号的槽函数中)后,你首先会调用这个方法来检查请求是否成功。如果不是 NoError,你就可以根据具体的错误码来执行不同的错误处理逻辑。

  • 示例:比如,你可以检查错误码是否是 QNetworkReply::ContentNotFoundError(对应 HTTP 404),然后提示用户“您访问的页面不存在”。

2. 方法:errorString()

  • 这个方法为 error() 返回的错误代码提供了一个人类可读的、描述性的文本信息。

  • 当 error() 返回的不是 NoError 时,调用这个方法可以获取到一段详细的、易于理解的错误描述,比如 “Connection refused” 或 “Host not found”。

  • 使用场景:它通常与 error() 方法配合使用。在调试程序或者需要向用户显示错误信息时,errorString() 提供的信息比一个单纯的枚举值要友好和直观得多。

  • 示例:如果 error() 返回了 ConnectionRefusedError,那么 errorString() 可能会返回类似于 “The remote server refused the connection (the server is not accepting requests)” 的字符串,你可以直接把这个字符串显示在用户界面上。

3. 方法:readAll()

  • 这个方法从回复对象中一次性读取所有剩余的可用数据,并以 QByteArray 的形式返回。

  • 因为 QNetworkReply 是 QIODevice 的子类,所以它继承了所有关于读写的功能。这个方法会一次性将整个响应体(Response Body)的内容读取出来。

  • 使用场景:当服务器返回的数据量不大,或者你确定可以一次性处理完所有数据时,使用这个方法非常方便。例如,请求一个 JSON 接口或者一个小的文本文件,在收到 finished() 信号后,调用 readAll() 就能拿到全部内容。

  • 注意:对于非常大的数据(如文件下载),使用 read() 或 readLine() 进行流式读取是更好的选择,以避免占用过多内存。

4. 方法:header(QNetworkRequest::KnownHeaders header)

  • 文字描述:这个方法允许你从 HTTP 响应头中获取一个“已知的”、“标准的”头部字段的值。

  • 你需要传入一个 QNetworkRequest::KnownHeaders 枚举值来指定你想要哪个HTTP头部字段。

  • 该方法返回一个 QVariant,里面包含了该头部的解析值(如果是日期,会是 QDateTime;如果是内容类型,会是字符串等)。

  • 使用场景:当你需要检查服务器返回的特定元信息时使用。最典型的用法是检查 ContentTypeHeader 来确定返回数据的格式(如 “application/json”),或者检查 LocationHeader 来处理 HTTP 重定向。

3.4.核心信号——QNetworkReply::finished

QNetworkReply类(HTT响应类)有一个信号——finished,这个很重要啊。

首先,你需要理解 Qt 网络请求的 “异步” 本质。

  • 同步请求:你发送一个请求后,程序就会“卡”在那里,一直等到服务器返回数据,才继续往下执行。这就像你打电话给朋友问一个问题,你拿着电话不说话,一直等到他回答你。

  • 异步请求:你发送一个请求后,程序不会“卡住”,而是立刻继续执行后面的代码。当服务器的数据返回时,会通过一个 “信号” 来通知你。这就像你发短信问朋友问题,发完之后你就可以去干别的事了(喝茶、看书),等他回复你了,你的手机会 “响铃”(信号),你听到铃声再去看消息(处理数据)。

QNetworkReply::finished 就是这个 “闹钟铃声” 或 “短信提示音”。

触发时机

QNetworkReply::finished信号在以下情况下会被触发:

  1. 请求成功完成:当网络请求正常完成,并且接收到了服务器的完整响应(包括HTTP状态码、响应头和数据)时,也就是当所有HTTP响应数据(包括响应头和响应体)都已经被完整接收并准备好可供读取时,会触发finished信号。此时,你可以通过QNetworkReply的方法读取响应数据。

  2. 请求失败当网络请求过程中发生错误(例如,连接超时、主机找不到、SSL错误等)时,也会触发finished信号。此时,你可以通过QNetworkReply的error()方法获取错误信息,并且可能无法读取到有效的响应数据。

  3. 请求被取消:如果你调用了QNetworkReply的abort()方法或close()方法取消请求,那么也会触发finished信号。这表示请求被中途取消,不会继续等待服务器的响应。

  4. 请求重定向:当遇到HTTP重定向(如301、302状态码)时,Qt的网络模块会自动处理重定向(除非你设置了不自动重定向)。在最终的重定向请求完成(无论成功还是失败)后,会触发finished信号。注意:在重定向过程中,你可能会收到多个QNetworkReply对象的finished信号(每个重定向步骤都有一个),但通常你只需要关注最初发出的请求(或最终重定向到的请求)的finished信号。

  5. 网络断开等异常:如果在请求过程中网络连接断开,或者服务器在传输过程中中断连接,也会触发finished信号,并伴随着相应的错误。

重要细节

  • 信号触发意味着请求结束:一旦finished信号被触发,就意味着这个QNetworkReply对象所代表的网络请求已经结束,不会再有任何数据可读(除非已经读过了),也不会再有任何状态变化。

  • 与readyRead信号的区别:readyRead信号是在有数据可读时触发,可能会多次触发(因为数据是分块到达的)。而finished信号只触发一次,即在请求完全结束时。

  • 与errorOccurred信号的关系:Qt 5.15引入了errorOccurred信号(在Qt6中改为errorOccurred),它会在发生错误时触发。注意,errorOccurred信号触发后,finished信号仍然会触发。因此,通常我们只需要连接finished信号,在槽函数中检查错误并处理数据即可。

3.4.简单示例——HTTP客户端

我们创建一个新项目

大家千万别忘了下面这个

我们

 

此处展示的响应内容,通常可使用 QPlainTextEdit 组件进行呈现。

该组件能够保留文本的原始格式,便于用户直接查看未经渲染的 HTML 代码或其他文本结构。

而如果使用 QTextEdit 组件,由于它内置了对 HTML 的解析与渲染能力,最终呈现的将是经过格式处理后的视觉效果,而非原始代码形态。

QTextEdit 在背后执行了复杂的布局和样式计算,因此当需要处理较大或结构复杂的 HTML 内容时,可能会引起界面卡顿或响应延迟的问题。

我们现在就来编写代码

我们运行一下,我们去访问一下http://www.baidu.com

点击发送请求

这个程序就没有任何问题的。

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

相关文章:

  • Go语言:高效简洁的现代编程语言
  • 云南公司网站建设做企业网站 需要用服务器吗
  • 网上做公益的网站医疗网站 seo怎么做
  • 重庆有的设计网站企业移动端建设与网站建设
  • 【深度学习04】PyTorch:损失函数、优化器、模型微调、保存与加载
  • 定远建设局官方网站app应用程序开发公司
  • Coze源码分析-资源库-编辑知识库-后端源码-基础设施/存储层
  • JVM栈溢出和堆溢出哪个先满?
  • 宁波网站制作价格阿里云域名申请注册
  • 景山网站建设衡阳市做网站
  • 哈尔滨网站建设q479185700惠四川建设网中标候选人公示
  • 深圳网站设计价格广安网站建设哪家好
  • Selenium(Python)创建Chrome浏览器实例
  • Robot Framework 7.0 报告解析
  • MySQL `SELECT` 查询优化:原理 + 案例 + 实战总结
  • PHP Directory:全面解析与优化实践
  • 网站开发实训报告参考文献网站丢了数据库还在
  • securinets ctf quals 2025 web all
  • 基于jsp的网站开发开题报告企业推广方式隐迅推知名
  • asp商品网站源码电影网站制作模版
  • 微服务注册与监听
  • 网站需要审核吗外贸电商平台哪个网站最好
  • 一个网站如何做cdn加速器ps平面设计主要做什么
  • 前端测试模块
  • 从零开始构建HIDS主机入侵检测系统:Python Flask全栈开发实战
  • 做网站收费吗重庆网站建设方案
  • 网站无法打开的原因多个网站给一个网站推广
  • 瞥[信号与系统个人笔记]第二章 连续时间信号与系统的时域分析W
  • cesium126,230130,Editing Tileset Materials 编辑瓦片集材质,官方教程:
  • 医院网站加快建设方案汽车网站建设公司哪家好