警告
本节包含自动从C++翻译成Python的代码片段,可能包含错误。
DTLS客户端#
此示例演示了如何实现客户端DTLS连接。
注意
DTLS客户端示例应与DTLS服务器示例一起运行。
示例DTLS客户端可以建立一个或多个DTLS服务器连接。客户端DTLS连接由DtlsAssociation类实现。此类使用QUdpSocket
读取和写入数据报,以及QDtls
进行加密。
class DtlsAssociation(QObject): Q_OBJECT # public DtlsAssociation(QHostAddress address, quint16 port, connectionName) = QString() ~DtlsAssociation() def startHandshake(): # signals def errorMessage(message): def warningMessage(message): def infoMessage(message): def serverResponse(clientInfo, datagraam,): plainText) = QByteArray() # private slots def udpSocketConnected(): def readyRead(): def handshakeTimeout(): def pskRequired(auth): def pingTimeout(): # private name = QString() socket = QUdpSocket() crypto = QDtls() pingTimer = QTimer() ping = 0 Q_DISABLE_COPY(DtlsAssociation)
构造函数为新的DTLS连接设置了最小的TLS配置,并设置了服务器的地址和端口。
... configuration = QSslConfiguration.defaultDtlsConfiguration() configuration.setPeerVerifyMode(QSslSocket.VerifyNone) crypto.setPeer(address, port) crypto.setDtlsConfiguration(configuration) ...
将handshakeTimeout()
信号连接到handleTimeout()槽以处理握手阶段的丢包和重传。
... crypto.handshakeTimeout.connect(self.handshakeTimeout) ...
为了确保我们只接收来自服务器的数据报,我们将我们的UDP套接字连接到服务器。
... socket.connectToHost(address.toString(), port) ...
将QUdpSocket::readyRead()信号连接到readyRead()槽。
... socket.readyRead.connect(self.readyRead) ...
当与服务器建立了安全连接后,DtlsAssociation对象将使用计时器向服务器发送短暂的ping消息。
pingTimer.setInterval(5000) pingTimer.timeout.connect(self.pingTimeout)
startHandshake()开始与服务器进行握手。
def startHandshake(self): if socket.state() != QAbstractSocket.ConnectedState: infoMessage.emit(tr("%1: connecting UDP socket first ...").arg(name)) socket.connected.connect(self.udpSocketConnected) return if not crypto.doHandshake(socket): errorMessage.emit(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())) else: infoMessage.emit(tr("%1: starting a handshake").arg(name))
readyRead()槽读取服务器发送的数据报。
dgram = QByteArray(socket.pendingDatagramSize(), Qt.Uninitialized) bytesRead = socket.readDatagram(dgram.data(), dgram.size()) if bytesRead <= 0: warningMessage.emit(tr("%1: spurious read notification?").arg(name)) return dgram.resize(bytesRead)
如果握手已经完成,则解密此数据报。
if crypto.isConnectionEncrypted(): plainText = crypto.decryptDatagram(socket, dgram) if plainText.size(): serverResponse.emit(name, dgram, plainText) return if crypto.dtlsError() == QDtlsError.RemoteClosedConnectionError: errorMessage.emit(tr("%1: shutdown alert received").arg(name)) socket.close() pingTimer.stop() return warningMessage.emit(tr("%1: zero-length datagram received?").arg(name)) else:
否则,我们尝试继续握手。
if not crypto.doHandshake(socket, dgram): errorMessage.emit(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())) return
当握手完成时,我们发送第一条ping消息。
if crypto.isConnectionEncrypted(): infoMessage.emit(tr("%1: encrypted connection established!").arg(name)) pingTimer.start() pingTimeout() else:
pskRequired()槽提供了握手阶段所需的预共享密钥(PSK)。
def pskRequired(self, auth): Q_ASSERT(auth) infoMessage.emit(tr("%1: providing pre-shared key ...").arg(name)) auth.setIdentity(name.toLatin1()) auth.setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"))
注意
为了简化,pskRequired()的定义过于简化。有关QSslPreSharedKeyAuthenticator
类和如何正确实现此槽的详细说明,请参阅文档。
pingTimeout()向服务器发送加密的消息。
def pingTimeout(self): message = "I am %1, please, accept our ping %2" written = crypto.writeDatagramEncrypted(socket, message.arg(name).arg(ping).toLatin1()) if written <= 0: errorMessage.emit(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())) pingTimer.stop() return ping = ping + 1
在握手阶段,客户端必须处理可能的超时,这可能是由于丢包造成的。handshakeTimeout()槽重新发送握手消息。
def handshakeTimeout(self): warningMessage.emit(tr("%1: handshake timeout, trying to re-transmit").arg(name)) if not crypto.handleTimeout(socket): errorMessage.emit(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString()))
在客户端连接销毁之前,必须关闭其DTLS连接。
DtlsAssociation.~DtlsAssociation() if crypto.isConnectionEncrypted(): crypto.shutdown(socket)
错误消息、信息消息和来自服务器的解密响应通过UI显示。
colorizer = QString(QStringLiteral("<font color=\"%1\">%2")) def addErrorMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg("Crimson", message)) def addWarningMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg("DarkOrange", message)) def addInfoMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg("DarkBlue", message)) def addServerResponse(self, clientInfo, datagram,): QByteArray plainText) messageColor = "DarkMagenta" formatter = QStringLiteral("<br>---------------"() "<br>%1 received a DTLS datagram:<br> %2" "<br>As plain text:<br> %3") html = formatter.arg(clientInfo, QString.fromUtf8(datagram.toHex(' ')), QString.fromUtf8(plainText)) ui.serverMessages.insertHtml(colorizer.arg(messageColor, html))