32. 网络应用

一、网络应用

  在 PySide6 中,有一个子模块 QtNetwork。使用 QtNetwork 模块中的类可以获取主机的网络信息、进行 TCP 通信、进行 UDP 通信、基于 HTTP 进行通信。

  PySide6 提供了 QUdpSocketQTcpSocketQTcpServer 这 3 个类,它们封装了许多功能,能够帮助我们快速实现基于 UDP 和 TCP 的应用程序。它们相较于 Python 标准库中的 socket 模块使用起来也更加方便。

  我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

二、IP地址对象

  在 PySide6 中,使用 QHostAddress 类创建 IP 地址对象,通常与 QTcpSocket 类、QTcpServer 类、QUdpSocket 类搭配使用。QHostAddress 类位于 PySide6 的 QtNetwork 子模块下,其构造函数如下:

QHostAddress()
QHostAddress(ip4Address:int)
QHostAddress(ip6Address:QIPv6Address)
QHostAddress(copy:QHostAddress)
QHostAddress(address:QHostAddress.SpecialAddress)

  其中,参数 ip4Address 表示 IPv4 地址参数。参数 ip6Address 表示 IPv6 地址对象。参数 copy 表示 QHostAddress 类的实例对象。参数 addressQHostAddress.SpecialAddress 类型的枚举值,可以取值如下:

QHostAddress.SpecialAddress.Null                                                # 空地址对象,相当于QHostAddress()
QHostAddress.SpecialAddress.LocalHost                                           # IPV4的本地主机地址,相当于QHostAddress("127.0.0.1")
QHostAddress.SpecialAddress.LocalHostIPv6                                       # IPV6的本地主机地址,相当于QHostAddress("::1")
QHostAddress.SpecialAddress.Broadcast                                           # IPV4的广播地址,相当于QHostAddress("255.255.255.255")
QHostAddress.SpecialAddress.AnyIPv4                                             # IPV4的任意地址,相当于QHostAddress("0.0.0.0"),绑定此地址的套接字只接听IPv4接口
QHostAddress.SpecialAddress.AnyIPv6                                             # IPV6的任意地址,相当于QHostAddress("::"),绑定此地址的套接字只接听IPv6接口
QHostAddress.SpecialAddress.Any                                                 # 双栈任意地址,绑定此地址的套接字将同时监听IPv4和IPv6接口

  QHostAddress 类的常用方法如下所示:

# 实例方法
clear() -> None                                                                 # 清空,主机地址为空,将协议设置为UnknownNetworkProtocol
isBroadcast() -> bool                                                           # 获取是否为IPv4的广播地址(255.255.255.255)

# 判断两个地址是否等价
isEqual(address:QHostAddress, mode:QHostAddress.ConversionModeFlag=QHostAddress.ConversionModeFlag.TolerantConversion) -> bool
isGlobal() -> bool                                                              # 获胜是否为全局地址
isInSubnet(subnet:tuple[QHostAddress, int]) -> bool                             # 判断此IP在subnet描述的子网中,则返回True
isLinkLocal() -> bool                                                           # 如果地址是IPv4或IPv6的链路本地地址,则返回True
isLoopback() -> bool                                                            # 如果地址是IPv4或IPv6的回环地址,则返回True
isMulticast() -> bool                                                           # 如果地址是IPv4或IPv6的组播地址,则返回True
isNull() -> bool                                                                # 如果此主机地址对任何主机或接口都无效,则返回True
isSiteLocal() -> bool                                                           # 如果地址是IPv4或IPv6的本地站点地址,则返回True
isUniqueLocalUnicast() -> bool                                                  # 如果地址是IPv6的唯一单播地址,则返回True

setScopeId(id:str) -> None                                                      # 设置IPv6地址的作用域ID
# 返回IPv6地址的作用域ID。对于IPv4地址,或者如果地址不包含作用域ID,则返回空字符串
scopeId() -> str

setAddress(address:str) -> None                                                 # 设置地址
setAddress(ipv4Address:int) -> None                                             # 设置IPv4地址
setAddress(ip6Address:QIPv6Address) -> None                                     # 设置IPv6地址
setAddress(address:QHostAddress.SpecialAddress) -> None                         # 设置特殊地址

swap(other:QHostAddress) -> None                                                # 将该主机地址与其它主机地址交换

toIPv4Address(ok:bool=None) -> int                                              # 以数字形式返回IPv4地址
toIPv6Address() -> QIPv6Address                                                 # 以Q_IPV6ADDR结构返回IPv6地址  

toString() -> str                                                               # 以字符串返回地址

# 静态方法
static parseSubnet(subnet:str) -> tuple[QHostAddress, int]                      # 解析子网中包含的IP和子网信息,并返回该网络的网络前缀及其前缀长度

  使用 isEqual(address:QHostAddress, mode:QHostAddress.ConversionModeFlag=QHostAddress.ConversionModeFlag.TolerantConversion) 方法 判断两个地址是否等价,参数 modeQHostAddress.ConversionModeFlag 类型的枚举值,可以取值如下:

# 当比较两个不同协议的QHostAddress对象时,不要将IPv6地址转换成IPv4地址,这样它们总会被认为是不同的
QHostAddress.ConversionModeFlag.StrictConversion
QHostAddress.ConversionModeFlag.ConvertV4MappedToIPv4                           # 当比较QHostAddress对象时,可以将IPv4地址转换成IPv6地址
QHostAddress.ConversionModeFlag.ConvertV4CompatToIPv4                           # 当比较QHostAddress对象时,可以将IPv4地址转换成为兼容的IPv6地址
QHostAddress.ConversionModeFlag.ConvertLocalHost                                # 当比较QHostAddress对象时,可以将IPv6回环地址转换成为IPv4回环地址
QHostAddress.ConversionModeFlag.ConvertUnspecifiedAddress                       # 所有未指定的地址比较后为相等,即AnyIpV4、AnyIPv6、Any为等价地址
QHostAddress.ConversionModeFlag.TolerantConversion                              # 设置前面3个标志

三、主机信息查询

  在 PySide6 中,可以使用 QHostInfo 类、QNetworkInterface 类获取主机的网络信息,这些网络信息是网络通信应用的基本信息。QHostInfo 类和 QNetworkInterface 类都位于 QtNetwork 子模块下。

3.1、主机信息类

  在 PySide6 中,可以使用 QHostInfo 类查询主机的 IP 地址,或通过 IP 地址查询主机名。QHostInfo 类的构造函数如下:

QHostInfo(other:QHostInfo)
QHostInfo(lookupId:int=-1)

  其中,参数 other 表示 QHostInfo 类创建的实例对象。参数 lookupId 表示 该对象的识别号码,保持默认即可。

  QHostInfo 类的常用方法如下所示:

# 实例方法
setAddresses(addresses:Sequence[QHostAddress]) -> None                          # 设置管理的IP地址列表
addresses() -> list[QHostAddress]                                               # 获取与hostName()对应的主机关联的IP地址列表

setError(error:QHostInfo.HostInfoError) -> None                                 # 设置失败类型
error() -> QHostInfo.HostInfoError                                              # 如果主机查询失败,则返回失败类型
errorString() -> str                                                            # 如果主机查询失败,则返回描述错误的字符串

setHostName(name:str) -> None                                                   # 设置主机名
hostName() -> str                                                               # 获取通过IP地址查询到的主机名

setLookupId(id:int) -> None                                                     # 设置查找的ID
lookupId() -> int                                                               # 获取本地查询的ID   

swap(other:QHostInfo) -> None                                                   # 将其它主机信息与此主机信息交换

# 静态方法
static abortHostLookup(lookupId:int) -> None                                    # 中断主机查询

static fromName(name:str) -> QHostInfo                                          # 返回主机名对应的主机信息
static localHostName() -> str                                                   # 返回本机的主机名
static localDomainName() -> str                                                 # 返回本机的DNS域名

# 以异步的方式根据主机名查询主机的IP地址,并返回本次查找的ID,可用作abortHostLookup()的参数,receiver标志自定义的槽函数
static lookupHost(name:str, receiver:QObject, member:str) -> int

  用 setError(error:QHostInfo.HostInfoError) 方法 设置失败类型,参数 errorQHostInfo.HostInfoError 类型的枚举值,可以取值如下:

QHostInfo.HostInfoError.NoError                                                 # 没有错误
QHostInfo.HostInfoError.HostNotFound                                            # 没有找到与主机名关联的IP地址
QHostInfo.HostInfoError.UnknownError                                            # 发生未知错误

  新建一个 ui.py 文件,用来存放 UI 相关的代码。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QPlainTextEdit

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        self.plainTextEdit = QPlainTextEdit()                                   # 3.创建文本框,然后添加到垂直布局中
        layout.addWidget(self.plainTextEdit)
      
        self.plainTextEdit.setReadOnly(True)                                    # 4.设置只读

  新建一个 widget.py 文件,用来存放业务逻辑相关的代码。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtNetwork import QHostInfo

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        host_name = QHostInfo.localHostName()                                   # 3.获取本机名称
        self.__ui.plainTextEdit.appendPlainText(f"主机名称:{host_name}\n")

        host_info = QHostInfo.fromName(host_name)                               # 4.获取主机名对应的主机信息
        host_address_list = host_info.addresses()                               # 5.获取主机地址列表
        for host_address in host_address_list:                                  # 6.遍历主机地址列表
            self.__ui.plainTextEdit.appendPlainText(f"协议:{str(host_address.protocol())}")    # 7.获取协议
            self.__ui.plainTextEdit.appendPlainText(f"地址:{str(host_address.toString())}\n")  # 8.获取地址

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

3.2、网络接口类

  在 PySide6 中,可以使用 QNetworkInterface获得运行系统主机的所有 IP 地址和网络接口列表QNetworkInterface 类的构造函数如下:

QNetworkInterface()
QNetworkInterface(other:QNetworkInterface)

  其中,other 表示 QNetworkInterface 类创建的实例对象。

  QNetworkInterface 类的常用方法如下:

# 实例方法
addressEntries() -> list[QNetworkAddressEntry]                                  # 获取网络接口的的网络地址条目列表

flags() -> QNetworkInterface.InterfaceFlag                                      # 获取该网络接口关联的标志

hardwareAddress() -> str                                                        # 获取接口的低级硬件地址,在以太网中就是MAC地址
humanReadableName() -> str                                                      # 获取可以读懂的接口名称,如果名称不明确,则获取names()方法的返回值

index() -> int                                                                  # 获取网络接口的系统索引
name() -> str                                                                   # 获取网络接口的名称
type() -> InterfaceType                                                         # 获取网络接口的类型
maximumTransmissionUnit() -> int                                                # 获取该接口的最大传输单元

isValid() -> bool                                                               # 如果接口信息有效,则返回值为True

swap(other:QNetworkInterface) -> None                                           # 将该网络接口实例与其它网络接口实例交换

# 静态方法
static allAddresses() -> list[QHostAddress]                                     # 获取主机上所有的IP地址列表
static allInterfaces() -> list[QNetworkInterface]                               # 获取主机上所有的网络接口列表

static interfaceFromIndex(index:int) -> QNetworkInterface                       # 根据索引获取网络接口
static interfaceFromName(name:str) -> QNetworkInterface                         # 根据名称获取网络接口

static interfaceIndexFromName(name:str) -> int                                  # 根据名称获取网络接口的索引,如果没有该名称的接口,则返回0
static interfaceNameFromIndex(index:int) -> str                                 # 根据索引获取网络接口的名称,如果没有该索引的接口,则返回空字符串 

  在 PySide6 中,使用 QNetworkInterface 类的 addressEntries() 方法可 获取包含网络地址条目(QNetworkAddressEntry)对象的列表网络地址条目对象 中包含了 网络接口的 IP 地址子网掩码广播地址。当然,也可以使用 QNetworkAddressEntry 创建网络地址条目地址对象

  QNetworkAddressEntry 类位于 PySide6 的 QtNetwork 子模块下,其构造函数如下:

QNetworkAddressEntry()
QNetworkAddressEntry(other:QNetworkAddressEntry)

  其中,other 表示 QNetworkAddressEntry 类创建的实例对象。

  QNetworkAddressEntry 类的常用方法如下:

broadcast() -> QHostAddress                                                     # 获取网络接口的广播地址
setBroadcast(newBroadcast:QHostAddress) -> None                                 # 设置新的广播地址

clearAddressLifetime() -> None                                                  # 重置该地址的首选生存期和有效生存期,应用该方法后,isLifetimeKnown()返回False

setDnsEligibility(status:QNetworkAddressEntry.DnsEligibilityStatus) -> None     # 设置该地址的DNS资格标志
dnsEligibility() -> QNetworkAddressEntry.DnsEligibilityStatus                   # 获取该地址是否有资格在域名系统(DNS)或类似的名称解析机制中发布

setIp(newIp:QHostAddress) -> None                                               # 设置该网络接口的IP地址
ip() -> QHostAddress                                                            # 获取该网络接口的IPV4地址或IPV6地址

isLifetimeKnown() -> bool                                                       # 如果地址生存期已知,则返回值为True
isPermanent() -> bool                                                           # 如果该地址在此接口上是永久的,则返回值为True
isTemporary() -> bool                                                           # 如果该地址在此接口上是临时的,则返回值为True

setNetmask(newNetmask:QHostAddress) -> None                                     # 设置网络接口的子网掩码
netmask() -> QHostAddress                                                       # 获取网络接口的子网掩码

setAddressLifetime(preferred:QDeadlineTimer, validity:QDeadlineTimer) -> None   # 设置该地址的首选生存期和有效生存期
preferredLifetime() -> QDeadlineTimer                                           # 返回此地址弃用(不再首选)时的截止日期
validityLifetime() -> QDeadlineTimer                                            # 如果地址变为无效,则返回该地址的最后期限

setPrefixLength(length:int) -> None                                             # 设置该IP地址的前缀长度
prefixLength() -> int                                                           # 返回该IP地址的前缀长度

swap(other:QNetworkAddressEntry) -> None                                        # 将此网络地址实例与其它实例交换

  修改 widget.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtNetwork import QNetworkInterface

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        interface_list = QNetworkInterface.allInterfaces()                      # 3.获取所有网络接口
        for item in interface_list:                                             # 4.遍历所有网络接口
            self.__ui.plainTextEdit.appendPlainText(f"设备地址:{item.humanReadableName()}")            # 5.获取接口名称
            self.__ui.plainTextEdit.appendPlainText(f"硬件地址:{item.hardwareAddress()}")              # 6.获取接口的MAC地址
            self.__ui.plainTextEdit.appendPlainText(f"接口类型:{item.type()}\n")                       # 7.获取接口类型

            entry_list = item.addressEntries()                                  # 8.获取包含网络地址条目
            for entry in entry_list:                                            # 9.遍历网络地址条目
                self.__ui.plainTextEdit.appendPlainText(f"IP地址:{entry.ip().toString()}")             # 10.获取IP地址
                self.__ui.plainTextEdit.appendPlainText(f"子网掩码:{entry.netmask().toString()}")      # 11.获取子网掩码
                self.__ui.plainTextEdit.appendPlainText(f"广播地址:{entry.broadcast().toString()}\n")  # 12.获取广播地址

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

四、Socket端口

  为了让两个程序通过网络进行通信,二者均必须使用 Socket 套接字。Socket 的英文原义是 “孔” 或 “插座”,通常也称作为 “套接字”,它用于描述 IP 地址和端口号,它是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在 Internet 上的主机上一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个 Socket,并绑定到一个端口上,不同的端口对应于不同的服务。

  在 PySide6 中,提供了 QAbstractSocket 抽象类,该类提供了 Socket 通信的方法和信号。

QAbstractSocket类的继承关系

  QAbstractSocket 类的方法如下:

# 实例方法
abort() -> None                                                                 # 中止当前连接并重置套接字

# 使用QAbstractSocket.BindFlag模式绑定指定端口号的地址
bind(address:QHostAddress, port:interpreter=0, mode:QAbstractSocket.BindFlag=QAbstractSocket.BindFlag.DefaultForPlatform) -> bool

# 以异步方式连接到指定的IP地址和端口的TCP服务器,连接成功会发送connected()信号
connectToHost(address:QHostAddress, port:int, mode:QAbstractSocket.BindFlag=QIODeviceBase.OpenModeFlag.ReadWrite) -> None

# 此函数将尽可能多地从内部缓冲区写入底层网络套接字,而不会阻塞。如果有数据写入,则函数的返回值为True,否则返回值为False
flush() -> bool

setSocketError(socketError:QAbstractSocket.SocketError) -> None                 # 设置发生的错误类型
error() -> QAbstractSocket.SocketError                                          # 返回上次发生的错误类型

isValid() -> bool                                                               # 如果套接字有效且有用,则返回True,否则返回False

setLocalAddress(address:QHostAddress) -> None                                   # 设置本地套接字的主机地址
localAddress() -> QHostAddress                                                  # 如果可用,则返回本地套接字的主机地址,否则返回None

setLocalPort(port:int) -> None                                                  # 设置本地套接字的主机端口号
localPort() -> int                                                              # 如果可用,则返回本地套接字的主机端口号,否则返回0

setPauseMode(pauseMode:QAbstractSocket.PauseMode) -> None                       # 设置套接字的暂停模式
pauseMode() -> QAbstractSocket.PauseMode                                        # 返回套接字的暂停模式

setPeerAddress(address:QHostAddress) -> None                                    # 设置连接对等体的地址
peerAddress() -> QHostAddress                                                   # 如果套接字处于ConnectedState状态,则返回被连接对等体的地址,否则返回None

setPeerName(name:str) -> None                                                   # 设置连接对等体的名称
peerName() -> str                                                               # 返回有connectToHost()指定的连接名称

setPeerPort(port:int) -> None                                                   # 设置连接对等体的端口
peerPort() -> int                                                               # 如果套接字处于ConnectedState状态,则返回被连接对等体的端口

setProtocolTag(tag:str) -> None                                                 # 设置该套接字的协议标记
protocolTag() -> str                                                            # 获取高套接字的协议标记

setProxy(networkProxy:QNetworkProxy) -> None                                    # 设置套接字的网络代理
proxy() -> QNetworkProxy                                                        # 返回套接字的网络代理

setReadBufferSize(size:int) -> None                                             # 设置内部缓冲区的大小
readBufferSize() -> int                                                         # 返回内部缓冲区的大小

setSocketState(state:QAbstractSocket.SocketState) -> None                       # 设置套接字的状态
state() -> QAbstractSocket.SocketState                                          # 返回套接字的状态

socketType() -> QAbstractSocket.SocketType                                      # 返回套接字类型

# 虚拟方法
disconnectFromHost() -> None                                                    # 断开Socket连接,成功断开后发哦是那个disconnected()信号

# 继续在套接字上传输数据。此方法只应在套接字被设置为收到通知是暂停,并且收到通知后使用
resume() -> None

# 用本地套接字描述符socketDescriptor初始化QAbstractSocket。如果socketDescriptor被接受为有效的套接字描述符,则返回值为True
setSocketDescriptor(socketDescriptor:int, state:QAbstractSocket.SocketState=QAbstractSocket.SocketState.ConnectedState, openMode:QIODeviceBase.OpenModeFlag=QIODeviceBase.OpenModeFlag.ReadWrite) -> bool

setSocketOption(option:QAbstractSocket.SocketOption, value:object) -> None      # 将指定选项设置为有value描述的值
socketOption(option) -> QAbstractSocket.SocketOption                            # 返回指定选项的值

waitForConnected(msecs:int=30000) -> None                                       # 等待建立套接字连接,可设置等待时间,默认等待30s
waitForDisconnected(msecs:int=30000) -> None                                    # 等待断开套接字连接,可设置等待时间,默认等待30s

  QAbstractSocket 类的方法如下:

connected()                                                                     # 当使用connectToHost()方法连接到服务器时发送信号
disconnected()                                                                  # 当断开套接字连接时发送信号
errorOccurred(socketError:QAbstractSocket.SocketError)                          # 当套接字发生错误时发送信号
stateChanged(socketState:QAbstractSocket.SocketState)                           # 当套接字的状态发生变化时发送信号
proxyAuthenticationRequired(proxy:QNetworkProxy, authenticator:QAuthenticator)  # 当使用需要身份验证的代理时发送信号

# 继承与QIODevice类的信号
readyRead()                                                                     # 当缓冲区有新数据发送此信号

五、基于TCP协议通信

  TCP(Transmission Control Protocol)表示传输控制协议,这是一种面向连接的可靠的传输协议。在 PySide6 中,可以使用 QTcpServer 类、QTcpSocket 类创建 TCP 通信程序。

  TCP 通信程序主要采用了客户机/服务器模式(Client/Server),即客户向服务器发出请求,服务器收到请求后,提供相应的服务。服务器端程序使用 QTcpServer 类进行端口监听,建立服务器。使用 QTcpSocket 建立连接,然后使用套接字(Socket)进行通信。

  在 PySide6 中,QTcpServer 类主要应用在服务器上进行网络监听,创建 Socket 连接。QTcpServer 类位于 QtNetwork 子模块下,其构造函数如下:

QTcpServer(parent:QObject=None)

  其中,parent 表示 QObject 类及其子类创建的实例对象。

  QTcpServer 类的常用方法如下:

# 实例方法
addPendingConnection(socket:QTcpSocket) -> None                                 # 由incomingConnection()调用,创建QTcpSocket对象并添加到内部可用的新连接列表
close() -> None                                                                 # 关闭服务器,停止网络监听
errorString() -> str                                                            # 获取可描述的最近发生的错误

isListening() -> bool                                                           # 如果服务器处于监听状态,则返回值为True
listen(address:QHostAddress=QHostAddress.Any, port:int=0) -> bool               # 监听指定的IP地址和端口号,若成功,则返回值为True

setListenBacklogSize(size:int) -> None                                          # 设置待接收连接的队列大小
listenBacklogSize() -> int                                                      # 获取待接收连接的队列大小

setMaxPendingConnections(numConnections:int) -> None                            # 设置可接收连接的最大数目
maxPendingConnections() -> int                                                  # 获取可接收连接的最大数目

pauseAccepting() -> None                                                        # 暂停接收新连接,排队的连接将保持在队列中
resumeAccepting() -> None                                                       # 恢复接收新连接

setProxy(networkProxy:QNetworkProxy) -> None                                    # 设置该套接字的网络代理
proxy() -> QNetworkProxy                                                        # 获取该套接字的网络代理

serverAddress() -> QHostAddress                                                 # 如果服务器正在侦听连接,则返回服务器的地址
serverPort() -> int                                                             # 如果服务器正在侦听连接,则返回服务器的端口,否则返回0
serverError() -> QAbstractSocket.SocketError                                    # 返回上次发生错误的错误代码

setSocketDescriptor(socketDescriptor:int) -> bool                               # 设置此服务器在监听到socketDescriptor的传入连接时应该使用的套接字描述符
socketDescriptor() -> int                                                       # 返回服务器用来监听传入指令的本机套接字描述符

waitForNewConnection(msec:int) -> bool                                          # 以阻塞的方式等待新的连接

# 虚拟方法
hasPendingConnections() -> bool                                                 # 如果服务器有一个观其的连接,则返回True,否则返回False
nextPendingConnection() -> QTcpSocket                                           # 返回下一个等待接入的连接

# 当有一个新连接可用时,QTcpServer内部调用此函数,创建一个QTcpSocket对象,将其添加到内部可用新连接列表,然后发送newConnection()信号
incomingConnection(handle:int) -> None   

  QTcpServer 类的常用信号如下:

acceptError(socketError:QAbstractSocket.SocketError)                            # 当接收一个新连接发生错误时发送信号
newConnection()                                                                 # 当有新连接时发送信号
pendingConnectionAvailable()                                                    # 当一个新的连接被添加到挂起的连接队列中时发送信号

  在 PySide6 中,可以使用 QTcpServer 类的 nextPendingConnection() 方法接受客户端的连接,然后使用 QTcpSocket 对象与客户端进行通信,具体的数据通信是通过 QTcpSocket 对象完成的。

  QTcpSocket 类提供了 TCP 的接口,可以使用 QTcpSocket 类实现标准的网络通信协议,例如 POP3SMTPNNTP,也可以使用自定义协议。QTcpSocket 类位于 PySide6 的 QtNetwork 子模块下。

  新建一个 tcp_server.py 文件,用来存放 TCP 服务端的代码。

from PySide6.QtGui import QCloseEvent
from PySide6.QtNetwork import QTcpServer, QTcpSocket, QHostAddress

class TcpServer:
    def __init__(self, host:str, port:int):
        self.host = QHostAddress(host)
        self.port = port
      
        self.client_set = set()                                                 # 1.定义客户端集合

        self.server = QTcpServer()                                              # 2.创建TCP套接字
      
        if self.server.listen(self.host, self.port):                            # 3.监听主机和端口
            self.server.newConnection.connect(self.new_connection)              # 4.有客户端的连接请求时,激活信号
      
    def new_connection(self):
        socket = self.server.nextPendingConnection()                            # 1.获取一个连接到客户端的QTcpSocket对象

        address, port = self.get_address_and_port(socket)                       # 2.获取地址和端口号

        data = f"已加入聊天框".encode("utf-8")
        self.send_to_clients(socket, data)                                      # 3.向每个客户端发送消息

        self.client_set.add(socket)                                             # 4.将socket对象添加到客户端集合中

        socket.readyRead.connect(lambda: self.receive(socket))                  # 5.当有新数据等待读取时激活信号
        socket.disconnected.connect(lambda: self.disconnected(socket))          # 6.当客户端断开连接时激活信号

    def disconnected(self, socket:QTcpSocket):
        address ,port = self.get_address_and_port(socket)                       # 1.获取地址和端口号

        data = f"离开聊天框".encode("utf-8")
        self.send_to_clients(socket, data)                                      # 2.向每个客户端发送消息

        self.client_set.remove(socket)                                          # 3.移除客户端

    def receive(self, socket:QTcpSocket):
        while socket.bytesAvailable():                                          # 1.判断是否存在待读取的数据报
            data_size = socket.bytesAvailable()                                 # 2.获取数据报的大小
            data = socket.read(data_size)                                       # 3.读取数据报
            self.send_to_clients(socket, data)                                  # 4.向每个客户端发送消息

    def send_to_clients(self, socket, data):
        current_client_address, current_client_port = self.get_address_and_port(socket)     # 1.获取当前客户端的地址和端口

        message = f"【{current_client_address}:{current_client_port}】:{str(data, 'utf-8')}".encode("utf-8")

        for client in self.client_set:                                          # 2.循环客户端集合
            client.write(message)                                               # 3.向每个客户端发送消息

    def get_address_and_port(self, socket:QTcpSocket):
        address = socket.peerAddress().toString()                               # 1.获取客户端地址
        port = socket.peerPort()                                                # 2.获取客户端端口
        return address, port

    def closeEvent(self, event:QCloseEvent):
        self.server.close()                                                     # 1.关闭服务器
        event.accept()                                                          # 2.接受关闭事件

  新建一个 tcp_client.py 文件,用来存放 TCP 客户端的代码。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout
from PySide6.QtWidgets import QTextBrowser, QTextEdit, QPushButton
from PySide6.QtGui import QCloseEvent
from PySide6.QtNetwork import QTcpSocket, QHostAddress

class TcpClient(QWidget):
    def __init__(self, remote_host:str, remote_port:int):
        super(TcpClient, self).__init__()                                       # 1.调用父类Qwidget类的__init__()方法

        self.remote_host = QHostAddress(remote_host)
        self.remote_port = remote_port

        self.setupUi()                                                          # 2.调用setupUi()方法初始化页面

        self.socket = QTcpSocket()                                              # 3.创建TCP套接字
        self.socket.connectToHost(self.remote_host, self.remote_port)           # 4.连接客户端(三次握手)

        self.clear_button.clicked.connect(self.clear_data)                      # 5.点击按钮激活信号
        self.send_button.clicked.connect(self.send)

        self.socket.readyRead.connect(self.receive)                             # 6.当有新数据等待读取时激活信号
      
    def setupUi(self):
      
        self.resize(800, 600)                                                   # 1.设置窗口对象大小

        vertical_layout = QVBoxLayout(self)                                     # 2.创建一个垂直布局

        self.receive_textBrowser = QTextBrowser()                               # 3.创建富文本框,用来显示接收的信息,然后添加到垂直布局中
        vertical_layout.addWidget(self.receive_textBrowser, 2)

        self.receive_textBrowser.setReadOnly(True)                              # 4.接收文本框设置只读
      
        self.send_textEdit = QTextEdit()                                        # 5.创建文本框,用来发送信息,然后添加到垂直布局中
        vertical_layout.addWidget(self.send_textEdit, 1)

        self.send_textEdit.setPlaceholderText("请输入发送的内容:")                # 6.设置发送文本框的提示信息

        horizontal_layout = QHBoxLayout()                                       # 7.创建水平布局,用来添加按钮,然后添加到垂直布局中
        vertical_layout.addLayout(horizontal_layout, 1)

        self.clear_button = QPushButton("清除")                                 # 8.创建按钮,用来清除消息
        horizontal_layout.addWidget(self.clear_button)

        self.send_button = QPushButton("发送")
        horizontal_layout.addWidget(self.send_button)

    def clear_data(self):
        self.send_textEdit.clear()

    def send(self):
        data = self.send_textEdit.toPlainText()                                 # 1.获取发送文本框内的内容
        if not data:
            return
     
        self.socket.write(data.encode("utf-8"))                                 # 2.将数据发送到服务端IP地址

        self.send_textEdit.clear()                                              # 3.清除发送文本框内容

    def receive(self):

        while self.socket.bytesAvailable():                                     # 1.判断是否存在待读取的数据报
            data_size = self.socket.bytesAvailable()                            # 2.获取数据报的大小
            data = self.socket.read(data_size)                                  # 3.读取数据报
            if data:
                self.receive_textBrowser.append(data.data().decode("utf-8"))    # 4.将数据报显示在接收文本框内

    def closeEvent(self, event:QCloseEvent):
        self.socket.close()                                                     # 1.关闭套接字
        event.accept()                                                          # 2.接受关闭事件

  新建一个 tcp_start.py 文件,用来启动 TCP。

import sys

from PySide6.QtWidgets import QApplication

from tcp_server import TcpServer
from tcp_client import TcpClient

if __name__ == "__main__":
    remote_address = "127.0.0.1"
    remote_port = 8000

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    tcp_server = TcpServer(remote_address, remote_port)

    udp_clients = []
    for i in range(3):
        udp_client = TcpClient(remote_address, remote_port)
        udp_client.show()
        udp_clients.append(udp_client)

    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

六、基于UDP协议通信

  UDP(User Datagram Protocol)表示用户数据报协议,这是一种轻量的不可靠的面向数据报的无连接的传输协议。UDP 协议被用于对可靠性要求不高的传输程序中。在 PySide6 中,可以使用 QUdpSocket 类创建 UDP 通信程序。

  与 TCP 通信不同,UDP 通信不区分客户端和服务器端,UDP 通信程序都是客户端程序,主要使用 QUdpSocket 类进行通信。与 QTcpSocket 类相同,QUdpSocket 类也是 QAbstractSocket 类的子类。QTcpSocket 类使用连续的数据流传输数据,而 QUdpSocket 类主要使用数据报传输数据,其构造函数如下:

QUdpSocket(parent:QObject=None)

  其中,parent 类表示 QObject 类及其子类创建的实例对象。

  使用 UDP 发送消息分为 单播广播组播 共 3 种模式。

  • 单播Unicast)表示 一个 UDP 客户端发出的数据报被传送到一个 UDP 客户端(指定地址、指定端口),是 一对一 的数据传输。
  • 广播Broadcast)表示 一个 UDP 客户端发出的数据报被传送到同一网络范围内的所有 UDP 客户端QUdpSocket 类支持 IPv4 广播,广播经常用于实现网络发现的协议。
  • 组播Multicast)也称为 多播,表示 一个 UDP 客户端加入一个由组播 IP 地址指定的多播组,该多播组中的一个 IP 地址接受组播的主机发送的数据报。任何一个成员向组播地址发送数据报,该组播的成员都可以接收到数据报,类似于 QQ 群的功能。

UDP客户端通信的3中模式

  QUdpSocket 类特有的方法如下:

# 实例方法
# 为UDP通信绑定一个端口
bind(address:QHostAddress.SpecialAddress, port:int=0, mode:QAbstractSocket.BindFlag=QAbstractSocket.BindFlag.DefaultForPlatform) -> bool

setMulticastInterface(iface:QNetworkInterface) -> None                          # 设置组播数据报的网络接口

joinMulticastGroup(groupAddress:QHostAddress) -> bool                           # 加入一个多播组
joinMulticastGroup(groupAddress:QHostAddress, iface:QNetworkInterface) -> bool  # 在指定的网络接口下加入一个多播组

leaveMulticastGroup(groupAddress:QHostAddress) -> bool                          # 离开一个多播组
leaveMulticastGroup(groupAddress:QHostAddress, iface:QNetworkInterface) -> bool # 离开指定网络接口的多播组

multicastInterface() -> QNetworkInterface                                       # 返回多播数据报的网络几口

hasPendingDatagrams() -> bool                                                   # 当至少有一个数据报需要读取时,返回True

pendingDatagramSize() -> int                                                    # 返回第1个待读取的数据报的大小

readDatagram(maxlen:int) -> tuple[bytes, QHostAddress, int]                     # 读取一个数据包,如成功,则返回该数据报的内容、地址、端口号

receiveDatagram(maxSize:int=-1) -> QNetworkDatagram                             # 接收一个不大于maxSize的数据报

writeDatagram(datagram:QNetworkDatagram) -> int                                 # 向指定地址和端口的UDP客户端发送数据包,如成功,则返回发送的字节数
writeDatagram(datagram:QByteArray, address:QHostAddress, port:int) -> int       # 向指定地址和端口的UDP客户端发送数据包,如成功,则返回发送的字节数

  新建一个 dup_main_client.py 文件,用来存放 UDP 主客户端的代码:

from PySide6.QtGui import QCloseEvent
from PySide6.QtNetwork import QUdpSocket, QHostAddress

class UdpMainClient:
    def __init__(self, host:str, port:int):
        self.host = QHostAddress(host)
        self.port = port

        self.sockct = QUdpSocket()                                              # 1.创建UDP套接字
        self.client_set = set()                                                 # 2.定义客户端集合

        if self.sockct.bind(self.host, self.port):                              # 3.绑定HOST和PORT
            self.sockct.readyRead.connect(self.receive)                         # 4.当有新数据等待读取时激活信号

    def receive(self):
        while self.sockct.hasPendingDatagrams():                                # 1.判断是否存在待读取的数据报
            data_size = self.sockct.pendingDatagramSize()                       # 2.获取数据报的大小
            data, host, port = self.sockct.readDatagram(data_size)              # 3.读取数据报

            host = host.toString()

            if data.contains("加入".encode("utf-8")):                           # 4.如果消息中有加入二字,则将主机和端口号添加到集合中
                self.client_set.add((host, port))
            elif data.contains("离开".encode("utf-8")):                         # 5.如果消息中有离开二字,则将主机和端口号从集合中删除
                self.client_set.remove((host, port))

            self.send_to_clients((host, port), data)                            # 6.向每个客户端发送消息

    def send_to_clients(self, current_client:tuple[str, int], data):
        message = f"【{current_client[0]}:{current_client[1]}】:{str(data, 'utf-8')}".encode("utf-8")
      
        for client in self.client_set:                                          # 1.循环客户端集合
            host, port = client                                                 # 2.获取主机和端口号
            self.sockct.writeDatagram(message, QHostAddress(host), port)        # 3.发送消息

    def closeEvent(self, event:QCloseEvent):
        self.sockct.close()                                                     # 1.关闭套接字
        event.accept()                                                          # 2.接受关闭事件

  新建一个 udp_client.py 文件,用来存放 UDP 客户端的代码。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout
from PySide6.QtWidgets import QTextBrowser, QTextEdit, QPushButton
from PySide6.QtGui import QCloseEvent
from PySide6.QtNetwork import QUdpSocket, QHostAddress

class UdpClient(QWidget):
    def __init__(self, remote_host:str, remote_port:int):
        super(UdpClient, self).__init__()                                       # 1.调用父类Qwidget类的__init__()方法

        self.remote_host = QHostAddress(remote_host)
        self.remote_port = remote_port

        self.socket = QUdpSocket()                                              # 2.创建UDP套接字

        self.setupUi()                                                          # 3.调用setupUi()方法初始化页面

        data = "你已经加入聊天室!\n"
        self.socket.writeDatagram(data.encode("utf-8"), self.remote_host, self.remote_port)   # 4.发送数据

        self.clear_button.clicked.connect(self.clear_data)                      # 5.点击按钮激活信号
        self.send_button.clicked.connect(self.send)
      
        self.socket.readyRead.connect(self.receive)                             # 6.当有新数据等待读取时激活信号

    def setupUi(self):
        self.resize(800, 600)                                                   # 1.设置窗口对象大小

        vertical_layout = QVBoxLayout(self)                                     # 2.创建一个垂直布局

        self.receive_textBrowser = QTextBrowser()                               # 3.创建富文本框,用来显示接收的信息,然后添加到垂直布局中
        vertical_layout.addWidget(self.receive_textBrowser, 2)

        self.receive_textBrowser.setReadOnly(True)                              # 4.接收文本框设置只读

        self.send_textEdit = QTextEdit()                                        # 5.创建文本框,用来发送信息,然后添加到垂直布局中
        vertical_layout.addWidget(self.send_textEdit, 1)

        self.send_textEdit.setPlaceholderText("请输入发送的内容:")                # 6.设置发送文本框的提示信息

        horizontal_layout = QHBoxLayout()                                       # 7.创建水平布局,用来添加按钮,然后添加到垂直布局中
        vertical_layout.addLayout(horizontal_layout)
      
        self.clear_button = QPushButton("清除")                                  # 8.创建按钮,用来清除消息
        horizontal_layout.addWidget(self.clear_button)

        self.send_button = QPushButton("发送")
        horizontal_layout.addWidget(self.send_button)

    def clear_data(self):
        self.send_textEdit.clear()

    def send(self):
        data = self.send_textEdit.toPlainText()                                 # 1.获取发送文本框内的内容
        if not data:
            return
      
        print(f"发送数据【{data}】给服务器")
        self.socket.writeDatagram(data.encode("utf-8"), self.remote_host, self.remote_port) # 2.将数据发送到服务端IP地址

        self.send_textEdit.clear()                                              # 3.清除发送文本框内容

    def receive(self):
        while self.socket.hasPendingDatagrams():                                # 1.判断是否存在待读取的数据报
            data_size = self.socket.pendingDatagramSize()                       # 2.获取数据报的大小
            data, host, port = self.socket.readDatagram(data_size)              # 3.读取数据报
            if data:
                message = data.data().decode("utf-8")
                print(message)
                self.receive_textBrowser.append(message)                        # 4.将数据报显示在接收文本框内
    def closeEvent(self, event:QCloseEvent):
        data = f"你离开了聊天室!\n"
        self.socket.writeDatagram(data.encode("utf-8"), self.remote_host, self.remote_port)     # 1.向服务端发送数据
        event.accept()  

  新建一个 udp_start.py 文件,用来存放 UDP 的启动代码。

import sys

from PySide6.QtWidgets import QApplication

from udp_main_client import UdpMainClient
from udp_client import UdpClient

if __name__ == "__main__":
    remote_address = "127.0.0.1"
    remote_port = 8000

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    udp_main_client = UdpMainClient(remote_address, remote_port)                # 2.创建一个UDP服务端

    udp_clients = []
    for i in range(3):
        udp_client = UdpClient(remote_address, remote_port)                     # 3.创建UDP客户端
        udp_client.show()                                                       # 4.显示UDP客户端的窗口
        udp_clients.append(udp_client)

    sys.exit(app.exec())                                                        # 5.进入程序的主循环并通过exit()函数确保主循环安全结束

七、基于HTTP协议通信

7.1、HTTP请求类

  在 PySide6 中,可以使用 QNetworkRequest 类向 指定的 URL 发送网络协议请求,也可以保存网络协议的请求信息,目前支持 HTTP、FTP、从 URL下载文件、向 URL上传文件。QNetworkRequest 类位于 QtNetwork 子模块下,其构造函数如下:

QNetworkRequest(url:QUrl)
QNetworkRequest(other:QNetworkRequest)

  QNetworkRequest 类的常用方法如下:

setAttribute(code:QNetworkRequest.Attribute, value:object) -> None              # 将与code关联的属性的值设置为value
attribute(code:QNetworkRequest.Attribute, defaultValue:object=None) -> object   # 返回与code关联的属性值,如果未设置该属性,则返回defaultValue

setDecompressedSafetyCheckThreshold(threshold:int) -> None                      # 设置存档检查的阈值
decompressedSafetyCheckThreshold() -> int                                       # 返回存档检查的阈值

setRawHeader(headerName:QByteArray, value:QByteArray) -> None                   # 设置原始报头headerName的值
hasRawHeader(headerName:str) -> bool                                            # 如果此网络请求重存在原始报头headerName,则返回True
rawHeader(headerName:QByteArray) -> QByteArray                                  # 返回headName的原始形式
rawHeaderList() -> list[QByteArray]                                             # 返回在该网络请求中设置的又有原始表头的列表

setHeader(header:QNetworkRequest.KnownHeaders, value:object) -> None            # 设置表头的值
header(header:QNetworkRequest.KnownHeaders) -> object                           # 返回已知网络报头的值(如果在此请求中存在)

setHttp2Configuration(configuration:QHttp2Configuration) -> None                # 设置HTTP/2连接的参数
http2Configuration() -> QHttp2Configuration                                     # 返回QNetworkAccessManager,用于请求及其底层HTTP/2连接的参数

setMaximumRedirectsAllowed(maximumRedirectsAllowed:int) -> None                 # 设置允许的最大重定向数
maximumRedirectsAllowed() -> int                                                # 返回允许的最大重定向数

setOriginatingObject(object:QObject) -> None                                    # 设置发起请求的对象的引用
originatingObject() -> QObject                                                  # 返回发起请求的对象的引用

setPeerVerifyName(peerName:str) -> None                                         # 设置证书验证的主机名
peerVerifyName() -> str                                                         # 返回证书验证设置的主机名

setPriority(priority:QNetworkRequest.Priority) -> None                          # 设置请求的优先级
priority() -> QNetworkRequest.Priority                                          # 返回该网络请求的优先级

setSslConfiguration(configuration:QSslConfiguration) -> None                    # 设置该网络请求的SSL配置
sslConfiguration() -> QSslConfiguration                                         # 返回该网络请求的SSL配置 

setTransferTimeout(timeout:int) -> None                                         # 设置传输超时时间(毫秒)
transferTimeout() -> int                                                        # 获取传输超时时间(毫秒)

setUrl(url:QUrl) -> None                                                        # 设置请求的URL
url() -> QUrl                                                                   # 获取请求的URL

swap(other:QNetworkRequest) -> None                                             # 将该网络请求与其它网络请求交换

7.2、HTTP网络操作类

  在 PySide6 中,QNetworkAccessManager 类可用于 协调网络,当使用 QNetworkRequest 类发起网络请求后,QNetworkAccessManager 类负责发送网络请求、创建网络响应。QNetworkAccessManager 类位于 PySide6 的 QtNetwork 子模块下,其构造函数如下:

QNetworkAccessManager(parent:QObject=None)

  其中,parent 表示 QObject 类及其子类创建的实例对象。

  QNetworkAccessManager 类的常用方法如下:

setAutoDeleteReplies(autoDelete:bool) -> None                                   # 设置是否自动删除QNetworkReply对象
autoDeleteReplies() -> bool                                                     # 如果当前配置为自动删除QNetworkReply对象,则返回True

setCache(cache:QAbstractNetworkCache) -> None                                   # 将管理器的网络缓存设置为缓存
cache() -> QAbstractNetworkCache                                                # 返回网络数据的缓存

clearAccessCache() -> None                                                      # 刷新身份验证数据和网络连接的内部缓存
clearConnectionCache() -> None                                                  # 刷新网络连接的内部缓存

connectToHost(hostName:str, port:int=80) -> None                                # 向指定端口的主机发送网络请求

# 这是一个重载函数,使用sslConfiguration配置向指定端口的主机发送网络请求
connectToHostEncrypted(hostName:str, port:int=443, sslConfiguration=QSslConfiguration, peerName:str) -> None

cookieJar() -> QNetworkCookieJar                                                # 返回存储从网络获得Cookie信息的QNetworkCookieJar对象

deleteResource(request:QNetworkRequest) -> QNetworkReply                        # 发送一个删除资源的请求

addStrictTransportSecurityHosts(knownHosts:Sequence[QHstsPolicy]) -> None       # 在HTTP缓存中添加HTTP严格传输安全策略

setStrictTransportSecurityEnabled(enabled:bool) -> None                         # 启用或禁用HTTP严格传输安全策略(HSTS)
isStrictTransportSecurityEnabled() -> bool                                      # 如果启用啦HTTP严格传输安全策略(HSTS),则返回值为True

enableStrictTransportSecurityStore(enabled:bool, storeDir="") -> None           # 如果enable为True,则内部HSTS存储将使用持久存储读取和写入HSTS信息
isStrictTransportSecurityStoreEnabled() -> bool                                 # 如果HSTS缓存使用永久存储来加载和存储HSTS策略,则返回值为True

get(request:QNetworkRequest) -> QNetworkReply                                   # 发送网络请求,并返回一个新的QNetworkReply对象
head(request:QNetworkRequest) -> QNetworkReply                                  # 发送网络请求,并返回包含headers信息的QNetworkReply对象

post(request:QNetworkRequest, multiPart:QHttpMultiPart) -> QNetworkReply        # 将消息的内容发送到指定的目的地
post(request:QNetworkRequest, data:QByteArray) -> QNetworkReply                 # 将消息的内容发送到指定的目的地
post(request:QNetworkRequest, data:QIODevice) -> QNetworkReply                  # 将消息的内容发送到指定的目的地

put(request:QNetworkRequest, multiPart:QHttpMultiPart) -> QNetworkReply         # 将消息的内容发送到指定的目的地
put(request:QNetworkRequest, data:QByteArray) -> QNetworkReply                  # 将消息的内容发送到指定的目的地
put(request:QNetworkRequest, data:QIODevice) -> QNetworkReply                   # 将消息的内容发送到指定的目的地

# 将自定义的请求发送到URL标识的服务器
sendCustomRequest(request:QNetworkRequest, verb:QByteArray, multiPart:QByteArray) -> QNetworkReply
sendCustomRequest(request:QNetworkRequest, verb:QByteArray, device:QIODevice) -> QNetworkReply

setProxy(proxy:QNetworkProxy) -> None                                           # 设置网络代理
proxy() -> QNetworkProxy                                                        # 获取网络代理

setProxyFactory(factory:QNetworkProxyFactory) -> None                           # 设置代理工厂
proxyFactory() -> QNetworkProxyFactory                                          # 获取代理工厂

setRedirectPolicy(policy:QNetworkRequest.RedirectPolicy) -> None                # 设置重定向政策
redirectPolicy() -> QNetworkRequest.RedirectPolicy                              # 获取重定向政策

setTransferTimeout(timeout:int) -> None                                         # 设置传输超时时间(毫秒)
transferTimeout() -> int                                                        # 获取传输超时时间(毫秒)

strictTransportSecurityHosts() -> list[QHstsPolicy]                             # 获取HTTP严格传输安全策略的列表

# 虚拟方法
# 返回一个新的QNetworkReply对象来管理操作和请求
createRequest(op:QNetworkAccessManager.Operation, request:QNetworkRequest, outgoingData:QIODevice=None) -> QNetworkReply

supportedSchemes() -> list[str]                                                 # 列出访问管理器支持的所有URL模式

# 槽方法
supportedSchemesImplementation() -> list[str]                                   # 列出访问管理器支持的所有URL模式

  QNetworkAccessManager 类的常用信号如下:

authenticationRequired(reply:QNetworkReply, authenticator:QAuthenticator)       # 当服务器响应请求的内容之前要求身份验证时发送信号
encrypted(reply:QNetworkReply)                                                  # 当SSL、TLS会话成功完成初始握手时发送信号
finished(reply:QNetworkReply)                                                   # 当网络响应完成时发送信号

# 当代理请求身份验证并且QNetworkAccessManager找不到有效的缓存凭据时,发送信号
preSharedKeyAuthenticationRequired(reply:QNetworkReply, authenticator:QSslPreSharedKeyAuthenticator)

sslErrors(reply:QNetworkReply, errors:list[QSslError])                          # 当SSL、TLS会话在设置过程中遇到错误时发送信号

7.3、HTTP响应类

  在 PySide6 中,使用 QNetworkReply 类表示网络请求的响应,当使用 QNetworkAccessManager 类的 post()get()put() 等方法发起网络请求后,返回的网络响应为 QNetworkReply 对象。QNetworkReply 类位于 PySide6 的 QtNetwork 子模块下,其构造函数如下:

QNetworkReply(parent:QObject=None)

  QNetworkReply 类的常用方法如下:

# 实例方法
setAttribute(code:QNetworkRequest.Attribute, value:object) -> None              # 设置与object关联的属性
attribute(code:QNetworkRequest.Attribute) -> object                             # 返回与object关联的属性

setError(errorCode:QNetworkReply.NetworkError , errorString:str) -> None        # 将错误条件设置为errorCode
error() -> QNetworkReply.NetworkError                                           # 返回在此出处理此请求时发现的错误

setHeader(header:QNetworkRequest.KnownHeaders, value:object) -> None            # 设置已知报头的值
header(header:QNetworkRequest.KnownHeaders) -> object                           # 返回已知报头header的值
hasRawHeader(headerName:str) -> bool                                            # 如果名称为headerName的原始报头是由服务器发送的,在返回值为True

rawHeader(headerName:str) -> QByteArray                                         # 返回服务器发送的报头为headerName的原始内容
setRawHeader(headerName:QByteArray, value:QByteArray) -> None                   # 设置指定报头的原始内容

rawHeaderList() -> list[QByteArray]                                             # 获取服务器发送的报头字段列表
rawHeaderPairs() -> list[QByteArray]                                            # 返回原始报文对的列表

ignoreSslErrors(errors:list[QSslError]) -> None                                 # 忽略参数给出的SSL错误

setFinished(finished:bool) -> None                                              # 设置是否完成请求
isFinished() -> bool                                                            # 如果响应完成或终止,则返回True

isRunning() -> bool                                                             # 如果请求仍在处理中,则返回值为True

manager() -> QNetworkAccessManager                                              # 返回用于创建该对象的QNetworkAccessManager对象

operation() -> QNetworkAccessManager.Operation                                  # 返回提交的操作

readBufferSize() -> int                                                         # 返回缓存区的大小(字节)

setRequest(request:QNetworkRequest) -> None                                     # 设置于该对象关联的网络请求
request() -> QNetworkRequest                                                    # 获取请求

setSslConfiguration(configuration:QSslConfiguration) -> None                    # 设置于该对象关联的SSL配置
sslConfiguration() -> QSslConfiguration                                         # 返回与该对象关联的SSL配置

setUrl(url:QUrl) -> None                                                        # 设置正在处理的URL
url() -> QUrl                                                                   # 获取正在下载或上传文件的URL

# 虚拟方法
setReadBufferSize(size:int) -> None                                             # 设置缓存区大小
abstract abort() -> None                                                        # 立即终止操作并关闭所有仍然打开的网络连接

setSslConfigurationImplementation(configuration:QSslConfiguration) -> None      # 提供该方法是为了重写setSslConfiguration()的行为
sslConfigurationImplementation(configuration:QSslConfiguration) -> None         # 提供该方法是为了重写sslConfiguration()的行为

ignoreSslErrors() -> None                                                       # 如果调用此函数,则将忽略与网络连接相关的SSL错误
ignoreSslErrorsImplementation(errors:list[QSslError]) -> None                   # 覆盖ignoreSslErrors()的行为

  QNetworkReply 类的常用信号如下:

downloadProgress(bytesReceived:int, bytesTotal:int)                             # 发出该信号是为了指示该函数请求的下载部分的进度
encrypted()                                                                     # 当SSL/TLS会话成功完成初始握手时发送信号
errorOccurred(code:QNetworkReply.NetworkError)                                  # 当检测到响应处理过程中出现错误时发送信号
finished()                                                                      # 当响应完成时发送信号
metaDataChanged()                                                               # 当响应中的元数据发生更改时发送信号
preSharedKeyAuthenticationRequired(authenticator:QSslPreSharedKeyAuthenticator) # 当SSL/TLS握手协商PSK密码套件需要PSK身份验证时发送信号
redirectAllowed()                                                               # 当允许重新定向时发送信号
requestSent()                                                                   # 当发送1次或多次请求时发送信号
socketStartedConnecting()                                                       # 当套接字连接时,并在发送请求之前,会发送0次或更多次信号
sslErrors(errors:list[QSslError])                                               # 如果SSL/TSL会话在设置过程中遇到错误,包括证书验证错误,则发送信号
uploadProgress(bytesSent:int, bytesTotal:int)                                   # 发送这个信号是为了指示这个网络请求的上传部分的进度

# 如果请求中没有设置ManuaRedirectPolicy,并且服务器响应3xx状态,并且location头中有一个有效的URL,则发送信号
redirected(url:QUrl)

  新建一个 ui.py 文件,用来存放 UI 相关的代码。

import os

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGridLayout
from PySide6.QtWidgets import QLabel, QLineEdit, QPushButton, QProgressBar
from PySide6.QtGui import Qt

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(600, 100)                                                 # 1.设置窗口对象大小

        layout = QGridLayout(window)                                            # 2.创建一个网格布局

        url_label = QLabel("URL:")                                             # 3.创建一个标签,然后添加到网格布局中
        layout.addWidget(url_label, 0, 0)

        self.url_lineEdit = QLineEdit()                                         # 4.创建一个单行文本框,然后添加到网格布局中
        layout.addWidget(self.url_lineEdit, 0, 1)

        self.url_lineEdit.setPlaceholderText("请输入URL地址")                    # 5.设置单行文本框的提示文本

        self.download_button = QPushButton("下载")                              # 6.创建一个按钮,然后添加到网格布局中
        layout.addWidget(self.download_button, 0, 2)

        save_file_path_label = QLabel("保存路径:")
        layout.addWidget(save_file_path_label, 1, 0)

        self.save_file_path_lineEdit = QLineEdit()
        layout.addWidget(self.save_file_path_lineEdit, 1, 1)

        self.save_file_path_lineEdit.setText(os.getcwd())

        self.choose_save_file_path_button = QPushButton("选择路径")
        layout.addWidget(self.choose_save_file_path_button)

        download_progress_label = QLabel("下载进度:")
        layout.addWidget(download_progress_label, 2, 0)

        self.progressBar = QProgressBar(minimum=0, maximum=100)                 # 7.创建一个进度条,然后添加到网格布局中
        layout.addWidget(self.progressBar, 2, 1, 1, 2)

        self.progressBar.setAlignment(Qt.AlignmentFlag.AlignRight)              # 8.设置进度条文本的显示位置

  新建一个 widget.py 文件,用来存放业务逻辑相关的代码。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QFileDialog, QMessageBox
from PySide6.QtCore import QUrl, QFile, QIODevice
from PySide6.QtNetwork import QNetworkRequest, QNetworkAccessManager

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.networkManager = QNetworkAccessManager()                           # 2.创建网络操作对象

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 3.初始化页面

        self.__ui.download_button.clicked.connect(self.down_file)               # 4.下载按钮被点击时触发信号
        self.__ui.choose_save_file_path_button.clicked.connect(self.choose_file_path)   # 5.选择文件按钮被点击时触发信号

    def down_file(self):
        url = self.__ui.url_lineEdit.text()                                     # 1.获取url字符串
        directory = self.__ui.save_file_path_lineEdit.text()                    # 2.获取目录字符串

        if not url:
            QMessageBox.information(self, "错误", "请输入URL地址")
            return
      
        if not directory:
            QMessageBox.information(self, "错误", "请选择一个有效的路径保存文件")
            return
      
        self.file_name = f"{directory}/{url.split("/")[-1]}"                    # 3.用来保存文件的全路径名

        url = QUrl.fromUserInput(url)                                           # 4.获取url地址
        if not url.isValid():
            QMessageBox.information(self, "错误", "该地址为无效的URL地址")
            return
      
        if QFile.exists(self.file_name):                                        # 5.如果文件存在,则删除文件
            QFile.remove(self.file_name)
      
        self.file = QFile(self.file_name)                                       # 6.创建一个文件对象
        if not self.file.open(QIODevice.OpenModeFlag.WriteOnly):                # 7.打开文件
            QMessageBox.information(self, "错误", "临时文件打开错误")
            return
      
        self.reply = self.networkManager.get(QNetworkRequest(url))              # 8.发送网络请求,创建网络连接

        self.__ui.download_button.setEnabled(False)                             # 9.失能按钮
        self.__ui.choose_save_file_path_button.setEnabled(False)

        self.__ui.url_lineEdit.setReadOnly(True)                                # 10.设置单行文本框为只读
        self.__ui.save_file_path_lineEdit.setReadOnly(True)

        self.reply.readyRead.connect(self.download_file)                        # 9.当缓冲区有新数据发送此信号
        self.reply.downloadProgress.connect(self.download_progress)             # 10.发出信号是指示网络请求的下载部分的进度
        self.reply.finished.connect(self.download_finshed)                      # 11.下载完成时发送信号
      
    def choose_file_path(self):
        directory = QFileDialog.getExistingDirectory()                          # 1.选择一个目录
        if directory:
            self.__ui.save_file_path_lineEdit.setText(directory)                # 2.设置单行文本框的文本

    def download_file(self):
        self.file.write(self.reply.readAll())                                   # 1.当文件中写入内容

    def download_progress(self, receive_size:int, total_size:int):
        self.__ui.progressBar.setMaximum(total_size)                            # 1.设置进度条的最大值
        self.__ui.progressBar.setValue(receive_size)                            # 2.设置进度条的当前值

    def download_finshed(self):
        self.__ui.download_button.setEnabled(True)                              # 1.使能按钮
        self.__ui.choose_save_file_path_button.setEnabled(True)

        self.__ui.url_lineEdit.setReadOnly(False)                               # 2.设置单行文本框为只读
        self.__ui.save_file_path_lineEdit.setReadOnly(False)

        self.file.close()                                                       # 3.关闭文件

        QMessageBox.information(self, "下载完成", f"{self.file_name}下载完成")     # 4.弹出一个消息框

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
posted @ 2025-01-20 20:02  星光映梦  阅读(88)  评论(0)    收藏  举报