Client DTLS
Cet exemple montre comment mettre en œuvre des connexions DTLS côté client.

Note : L'exemple de client DTLS est destiné à être exécuté en même temps que l'exemple de serveur DTLS.
L'exemple de client DTLS peut établir plusieurs connexions DTLS avec un ou plusieurs serveurs DTLS. Une connexion DTLS côté client est implémentée par la classe DtlsAssociation. Cette classe utilise QUdpSocket pour lire et écrire des datagrammes et QDtls pour le cryptage :
class DtlsAssociation : public QObject { Q_OBJECT public: DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); ~DtlsAssociation(); void startHandshake(); signals: void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText); private slots: void udpSocketConnected(); void readyRead(); void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); void pingTimeout(); private: QString name; QUdpSocket socket; QDtls crypto; QTimer pingTimer; unsigned ping = 0; Q_DISABLE_COPY(DtlsAssociation) };
Le constructeur définit la configuration TLS minimale pour la nouvelle connexion DTLS, ainsi que l'adresse et le port du serveur :
... auto configuration = QSslConfiguration::defaultDtlsConfiguration(); configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); ...
Le signal QDtls::handshakeTimeout() est connecté au slot handleTimeout() pour gérer la perte de paquets et la retransmission pendant la phase d'échange :
... connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); ...
Pour nous assurer que nous ne recevons que les datagrammes du serveur, nous connectons notre socket UDP au serveur :
... socket.connectToHost(address.toString(), port); ...
Le signal QUdpSocket::readyRead() est connecté au slot readyRead() :
... connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); ...
Lorsqu'une connexion sécurisée est établie avec un serveur, un objet DtlsAssociation envoie de courts messages ping au serveur, à l'aide d'une minuterie :
pingTimer.setInterval(5000); connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout);
startHandshake() démarre un handshake avec le serveur :
void DtlsAssociation::startHandshake() { if (socket.state() != QAbstractSocket::ConnectedState) { emit infoMessage(tr("%1: connecting UDP socket first ...").arg(name)); connect(&socket, &QAbstractSocket::connected, this, &DtlsAssociation::udpSocketConnected); return; } if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else emit infoMessage(tr("%1: starting a handshake").arg(name)); }
Le slot readyRead() lit un datagramme envoyé par le serveur :
QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized); const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size()); if (bytesRead <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; } dgram.resize(bytesRead);
Si la poignée de main est déjà terminée, ce datagramme est décrypté :
if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { emit serverResponse(name, dgram, plainText); return; } if (crypto.dtlsError() == QDtlsError::RemoteClosedConnectionError) { emit errorMessage(tr("%1: shutdown alert received").arg(name)); socket.close(); pingTimer.stop(); return; } emit warningMessage(tr("%1: zero-length datagram received?").arg(name)); } else {
sinon, on essaie de continuer la poignée de main :
if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; }
Lorsque la poignée de main est terminée, nous envoyons notre premier message ping :
if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); pingTimer.start(); pingTimeout(); } else {
Le slot pskRequired() fournit la clé prépartagée (PSK) nécessaire pendant la phase de poignée de main :
void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); emit infoMessage(tr("%1: providing pre-shared key ...").arg(name)); auth->setIdentity(name.toLatin1()); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); }
Note : Par souci de concision, la définition de pskRequired() est simplifiée à l'extrême. La documentation de la classe QSslPreSharedKeyAuthenticator explique en détail comment ce slot peut être correctement implémenté.
pingTimeout() envoie un message crypté au serveur :
void DtlsAssociation::pingTimeout() { static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); if (written <= 0) { emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); pingTimer.stop(); return; } ++ping; }
Pendant la phase d'échange, le client doit gérer les éventuels dépassements de délai, qui peuvent survenir en raison de la perte de paquets. Le slot handshakeTimeout() retransmet les messages de la poignée de main :
void DtlsAssociation::handshakeTimeout() { emit warningMessage(tr("%1: handshake timeout, trying to re-transmit").arg(name)); if (!crypto.handleTimeout(&socket)) emit errorMessage(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString())); }
Avant qu'une connexion client ne soit détruite, sa connexion DTLS doit être fermée :
DtlsAssociation::~DtlsAssociation() { if (crypto.isConnectionEncrypted()) crypto.shutdown(&socket); }
Les messages d'erreur, les messages d'information et les réponses décryptées des serveurs sont affichés par l'interface utilisateur :
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>")); void MainWindow::addErrorMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message)); } void MainWindow::addWarningMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message)); } void MainWindow::addInfoMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message)); } void MainWindow::addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText) { static const QString messageColor = QStringLiteral("DarkMagenta"); static const QString formatter = QStringLiteral("<br>---------------" "<br>%1 received a DTLS datagram:<br> %2" "<br>As plain text:<br> %3"); const QString html = formatter.arg(clientInfo, QString::fromUtf8(datagram.toHex(' ')), QString::fromUtf8(plainText)); ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); }
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.