Qt网络编程:QSctpSocket、QSctpServer、QSslSocket
QSctpSocket
一、描述
1.1、SCTP
1、SCTP(流控制传输协议)是一种传输层协议,其作用类似于流行的TCP 和 UDP协议。
2、与 UDP 一样,SCTP 也是面向消息的,但它确保消息的可靠、按序传输,并使用 TCP 等拥塞控制。
3、SCTP 是面向连接的协议,它提供端点之间多个数据流的完整同时传输。这种多流允许数据通过独立的通道传递,因此如果一个流中的数据丢失,则其他流的传递不会受到影响。
4、由于面向消息,SCTP 传输一系列消息,而不是像 TCP 那样传输不间断的字节流。就像在 UDP 中一样,在 SCTP 中,发送方在一次操作中发送一条消息,而该消息在一次操作中被传递到接收应用程序进程。但与 UDP 不同的是,交付是有保证的。
5、SCTP 支持多宿主,这意味着连接的端点可以具有与其关联的备用 IP 地址,以便绕过网络故障或不断变化的条件进行路由。
1.2、QSctpSocket描述
QSctpSocket 是 QTcpSocket 的子类,它可以在两种模式下运行:
连续字节流(TCP 仿真):要设置连续字节流模式,请实例化 QSctpSocket 并使用负值设置setMaximumChannelCount()。这提供了将 QSctpSocket 用作常规缓冲 QTcpSocket 的能力。可以调用connectToHost() 发起与端点的连接,write() 发送和read() 接收来自对等方的数据,但无法区分消息边界。
多流数据报模式:默认情况下,QSctpSocket 在数据报模式下运行。例:
QSctpSocket *socket = new QSctpSocket(this);
socket->setMaxChannelCount(16);
socket->connectToHost(QHostAddress::LocalHost, 1973);
if (socket->waitForConnected(1000)) {
int inputChannels = socket->readChannelCount();
int outputChannels = socket->writeChannelCount();
}
在数据报模式下,QSctpSocket 为每个通道独立执行数据报缓冲。您可以通过调用writeDatagram() 将数据报排队到当前通道的缓冲区,并分别通过调用 readDatagram() 读取待处理的数据报。
允许在数据报模式下使用标准 QIODevice 函数 read()、readLine()、write() 等,其限制与连续字节流模式相同。
注意:Windows 平台不支持此功能。
二、成员函数
1、bool isInDatagramMode()
socket是否以数据报模式运行。
2、QNetworkDatagram readDatagram()
从当前读取通道的缓冲区读取数据报,并将其作为 QNetworkDatagram 对象连同发送方的主机地址和端口一起返回。
3、void setMaximumChannelCount(int count)
设置在数据报模式下支持的最大通道数。如果为 0,则通道将由远程端点设置。负数则设置此socket为连续字节流模式。仅当 QSctpSocket 处于 UnconnectedState 时调用此方法。
4、bool writeDatagram(const QNetworkDatagram &datagram)
将数据报写入当前写入通道的缓冲区,写入成功返回真。
QSctpServer
一、描述
QSctpServer 是 QTcpServer 的子类,用来接收QSctpSocket连接。
在 TCP 仿真模式下,接受的客户端使用单个连续字节流进行数据传输,而 QSctpServer 就像一个普通的 QTcpServer。调用 nextPendingConnection() 来接受挂起的连接作为连接的 QTcpSocket。该函数返回一个指向 QAbstractSocket::ConnectedState 中的 QTcpSocket 的指针,可以使用该指针与客户端进行通信。此模式只允许访问基本的 SCTP 协议功能。socket在底层级通过 IP 传输 SCTP 数据包,并通过 QTcpSocket 接口与应用程序交互。
相比之下,数据报模式是面向消息的,并提供端点之间多个数据流的完整同时传输。
注意:Windows 平台不支持此功能。
二、成员函数
1、void setMaximumChannelCount(int count)
设置服务器准备在数据报模式下支持的最大通道数。如果为0意味着连接通道的数量将由远程端点设置。负数则设置 TCP 仿真模式。
仅当 QSctpServer 处于 UnconnectedState 时调用此方法。
2、QSctpSocket *nextPendingDatagramConnection()
将下一个挂起的连接作为已连接的 QSctpSocket 对象返回。QSctpSocket 对象的控制权交给调用者。
服务器内部的socket 是作为服务器的子节点(child)创建的,QSctpServer 对象被销毁时它会被自动删除。
注意:返回的 QSctpSocket 对象不能被另一个线程使用。如果想要使用来自另一个线程的传入连接,需要重写QSctpServer::incomingConnection()。
打算系统学习qt开发的朋友可以看这一篇文章:
QSslSocket
一、描述
QSslSocket 建立安全、加密的 TCP 连接,您可以使用它来传输加密数据。它可以在客户端和服务器模式下运行,支持 SSL 协议,包括 SSL 3 和 TLS 1.2。 默认情况下,QSslSocket 仅使用被认为是安全的 SSL 协议 (QSsl::SecureProtocols),但您可以通过调用 setProtocol() 来更改 SSL 协议,只要您在握手开始之前执行此操作即可。
在Socket进入 ConnectedState 后,SSL 加密在现有 TCP 流之上运行。有两种使用 QSslSocket 建立安全连接的简单方法:使用立即 SSL 握手,或在未加密模式下建立连接后延迟 SSL 握手。
使用 QSslSocket 最常见的方法是构造一个对象并通过调用 connectToHostEncrypted() 启动安全连接。一旦建立连接,此方法立即开始 SSL 握手。
- QSslSocket *socket = new QSslSocket(this);
- connect(socket, SIGNAL(encrypted()), this, SLOT(ready()));
- socket->connectToHostEncrypted("http://imap.example.com", 993);
与普通的 QTcpSocket 一样,如果连接成功,QSslSocket 会进入 HostLookupState、ConnectingState 和最后的 ConnectedState状态。然后握手自动开始,如果成功,则发出 encrypted() 信号以指示Socket已进入加密状态并准备好使用。
请注意,数据可以在从 connectToHostEncrypted() 返回后立即写入Socket(即,在发出 encrypted() 信号之前)。数据在 QSslSocket 中排队,直到发出 encrypted() 信号再进行处理。
使用延迟 SSL 握手保护现有连接的示例是 SSL 服务器保护传入连接的情况。假设创建一个QTcpServer 的子类作为 SSL 服务器。可以进行:
void SslServer::incomingConnection(qintptr socketDescriptor)
QSslSocket *serverSocket = new QSslSocket;
if (serverSocket->setSocketDescriptor(socketDescriptor))
addPendingConnection(serverSocket);
connect(serverSocket, &QSslSocket::encrypted, this, &SslServer::ready);
serverSocket->startServerEncryption();
delete serverSocket;
}
当有客户端连接服务端时会调用QTcpServer::incomingConnection(),重写此函数可以实现自己的服务器操作。这里首先构造 QSslSocket 的实例,然后调用 setSocketDescriptor() 将新Socket的描述符设置为传入的现有Socket。然后通过调用启动 SSL 握手启动服务器加密()。
如果发生错误,QSslSocket 会发出 sslErrors() 信号。在这种情况下,如果不采取任何措施来忽略错误,则连接将被丢弃。发生错误要继续操作时,可以调用 ignoreSslErrors(),这将允许 QSslSocket 在建立对等方的身份时忽略它遇到的错误。在 SSL 握手期间忽略错误应该谨慎使用,因为安全连接的一个基本特征是它们应该通过成功的握手来建立。
加密后,可以将 QSslSocket 当做常规 QTcpSocket使用。
QSslSocket 支持 QTcpSocket 的阻塞函数 waitForConnected()、waitForReadyRead()、waitForBytesWritten() 、 waitForDisconnected()。它还提供了waitForEncrypted(),此函数将阻塞调用线程,直到建立了加密连接。
bytesWritten() 信号和 encryptedBytesWritten() 信号之间的区别:
对于 QTcpSocket,bytesWritten() 将在数据写入 TCP Socket后立即发出。
对于 QSslSocket,bytesWritten() 将在数据被加密时发出,而 encryptedBytesWritten() 将在数据写入 TCP Socket后立即发出。
二、类型成员
1、QSslSocket::PeerVerifyMode:描述 QSslSocket 的对等验证模式。
- VerifyNone:QSslSocket 不会向对等方请求证书。如果对连接另一端的身份不感兴趣,可以设置此模式。连接仍将被加密,并且如果对等方请求,本地Socket仍将其本地证书发送给对等方。
- QueryPeer:QSslSocket 将从对等方请求证书,但不要求此证书有效。当希望向用户显示对等证书详细信息而不影响实际 SSL 握手时,这很有用。此模式是服务器的默认模式。
- VerifyPeer:QSslSocket 会在 SSL 握手阶段向对端请求证书,并要求该证书有效。失败时,QSslSocket 将发出 QSslSocket::sslErrors() 信号。此模式是客户端的默认模式。
- AutoVerifyPeer:默认设置,QSslSocket 将自动为服务器Socket使用 QueryPeer,为客户端Socket使用 VerifyPeer。
2、QSslSocket::SslMode:描述 QSslSocket 可用的连接模式。
- UnencryptedMode:Socket未加密。 它的行为与 QTcpSocket 相同。
- SslClientMode:Socket是客户端 SSL Socket。
- SslServerMode:Socket是服务器端 SSL Socket。
三、成员函数
1、[signal] void encrypted()
当 QSslSocket 进入加密模式时会发出此信号。发出此信号后, isEncrypted() 将返回 true,并且Socket上的所有进一步传输都将被加密。
2、[signal] void encryptedBytesWritten(qint64 written)
当 QSslSocket 将其加密数据写入网络时,会发出此信号。参数为成功写入的字节数。
3、void ignoreSslErrors()
指示QSslSocket 握手阶段忽略错误并继续连接。如果想在握手阶段发生错误的情况下继续连接,那么您必须从连接到 sslErrors() 信号的槽函数或在握手阶段之前调用此函数。如果不调用此插槽,无论是响应错误还是在握手之前,都将在发出 sslErrors() 信号后断开连接。
4、[signal] void modeChanged(QSslSocket::SslMode mode)
当模式从 QSslSocket::UnencryptedMode 更改为 QSslSocket::SslClientMode 或 QSslSocket::SslServerMode 时,会发出此信号。参数是新模式。
5、[signal] void newSessionTicketReceived()
如果在握手期间协商了 TLS 1.3 协议,则 QSslSocket 在收到 NewSessionTicket 消息(服务器收到客户端Finished消息后,它都可以发送NewSessionTicket消息)后发出此信号。
此功能仅在 OpenSSL 后端启用,并且需要 OpenSSL v 1.1.1 或更高版本。
6、[signal] void peerVerifyError(const QSslError &error)
在建立加密之前,QSslSocket 可以在 SSL 握手期间多次发出此信号,以指示在建立对等方身份时发生了错误。该错误通常表示 QSslSocket 无法安全地识别对等方。通过连接到这个信号,可以在握手完成之前手动选择从连接的槽函数内部拆除连接。如果没有采取任何行动,QSslSocket 将继续发出 QSslSocket::sslErrors()。
7、[signal] void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator)
QSslSocket 在协商 PSK 密码套件时发出此信号,因此需要 PSK 身份验证。
使用 PSK 时,客户端必须向服务器发送一个有效的身份和一个有效的预共享密钥,以便 SSL 握手继续进行。 应用程序可以在连接到这个信号的槽中提供这个信息,根据他们的需要填充传递的验证器对象。
注意:忽略此信号,或未能提供所需的凭据,将导致握手失败,从而中止连接。
注意:authenticator 对象归Socket所有,不得被应用程序删除。
8、[signal] void sslErrors(const QList &errors)
QSslSocket 在 SSL 握手后发出此信号以指示在建立对等方身份时发生了错误。这些错误通常表明 QSslSocket 无法安全地识别对等方。除非采取行动,否则在发出此信号后将断开连接。参数是发生的错误列表。
如果想在发生错误的情况下继续连接,您必须从连接到此信号的槽函数内部调用 ignoreSslErrors()。如果以后需要访问错误列表,可以调用 sslHandshakeErrors()。
注意:连接到该信号时不能使用 Qt::QueuedConnection,否则调用 ignoreSslErrors() 将不起作用。
9、void startClientEncryption()
为客户端连接启动延迟 SSL 握手。当Socket处于 ConnectedState 但仍处于 UnencryptedMode 时,可以调用此函数。 如果尚未连接,或者已经加密,则此功能无效。
10、void startServerEncryption()
为服务器连接启动延迟 SSL 握手。 当Socket处于 ConnectedState 但仍处于 UnencryptedMode 时,可以调用此函数。 如果未连接或已加密,则该功能无效。
对于服务器Socket,调用此函数是发起 SSL 握手的唯一方法。大多数服务器会在收到连接后立即调用此函数,或者由于收到了进入 SSL 模式的特定于协议的命令。
实现 SSL 服务器的最常见方法是创建 QTcpServer 的子类并重新实现 QTcpServer::incomingConnection()。 然后将返回的Socket描述符传递给setSocketDescriptor()。
11、void abort()
中止当前连接并重置Socket。与 disconnectFromHost() 不同,该函数会立即关闭Socket,清除写入缓冲区中的所有挂起数据。
12、qint64 bytesAvailable()
返回可立即读取的解密字节数。
13、qint64 bytesToWrite()
返回等待加密并写入网络的未加密字节数。
14、void connectToHostEncrypted(const QString &hostName, quint16 port, QIODevice::OpenMode mode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
在端口上启动到设备主机名的加密连接。这相当于调用 connectToHost() 建立连接,然后调用 startClientEncryption()。 protocol 参数可用于指定要使用的网络协议(例如 IPv4 或 IPv6)。
QSslSocket 首先进入HostLookupState。然后,在进入事件循环或 waitFor...() 函数之一后,它进入 ConnectingState,发出 connected(),然后启动 SSL 客户端握手。在每次状态更改时,QSslSocket 都会发出信号 stateChanged()。
发起 SSL 客户端握手后,如果无法建立对等方的身份,则发出信号 sslErrors()。如果要忽略错误并继续连接,则必须从连接到 sslErrors() 信号的槽函数内部或在进入加密模式之前调用 ignoreSslErrors()。如果ignoreSslErrors()没有被调用,连接被丢弃,将发出disconnected()信号,并进入UnconnectedState状态。
如果 SSL 握手成功,QSslSocket 会发出 encrypted()信号。
15、void connectToHostEncrypted(const QString &hostName, quint16 port, const QString &sslPeerName, QIODevice::OpenMode mode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
重载函数。使用主机名 (sslPeerName) 进行证书验证。
16、qint64 encryptedBytesAvailable()
返回等待解密的加密字节数。 通常此函数返回0,因为QSslSocket会尽快解密其传入数据。
17、qint64 encryptedBytesToWrite()
返回等待写入网络的加密字节数。
18、bool flush()
此函数尽可能多地从内部写入缓冲区写入底层网络Socket。如果写入了任何数据,此函数返回true。
如果需要QSslSocket立即开始发送缓冲数据,请调用此函数。成功写入的字节数取决于操作系统。在大多数情况下,您不需要调用此函数,因为一旦控件返回到事件循环,QAbstractSocket将自动开始发送数据。如果没有事件循环,则应该调用waitForBytesWritten()。
19、void ignoreSslErrors(const QList &errors)
此方法告诉 QSslSocket 将忽略列表中给出的错误。
注意:由于大多数 SSL 错误都与证书相关联,因此对于其中的大多数错误,必须设置与此 SSL 错误相关的预期证书。 例如,如果想连接到使用自签名证书的服务器,可以考虑:
QList<QSslCertificate> cert = QSslCertificate::fromPath(QLatin1String("server-certificate.pem"));
QSslError error(QSslError::SelfSignedCertificate, cert.at(0));
QList<QSslError> expectedSslErrors;
expectedSslErrors.append(error);