DTLS-Client

Dieses Beispiel veranschaulicht die Implementierung clientseitiger DTLS-Verbindungen.

Screenshot des DTLS-Client-Beispiels.

Hinweis: Das DTLS-Client-Beispiel ist für die Ausführung zusammen mit dem DTLS-Server -Beispiel vorgesehen.

Der DTLS-Client aus diesem Beispiel kann mehrere DTLS-Verbindungen zu einem oder mehreren DTLS-Servern herstellen. Eine clientseitige DTLS-Verbindung wird durch die Klasse „DtlsAssociation“ implementiert. Diese Klasse verwendet „ QUdpSocket “ zum Lesen und Schreiben von Datagrammen und „ QDtls “ zur Verschlüsselung:

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)
};

Der Konstruktor legt die minimale TLS-Konfiguration für die neue DTLS-Verbindung fest und bestimmt die Adresse sowie den Port des Servers:

    ...
auto configuration = QSslConfiguration::defaultDtlsConfiguration();
configuration.setPeerVerifyMode(QSslSocket::VerifyNone);
crypto.setPeer(address, port);
crypto.setDtlsConfiguration(configuration);
    ...

Das Signal ` QDtls::handshakeTimeout()` ist mit dem Slot `handleTimeout()` verbunden, um Paketverluste und erneute Übertragungen während der Handshake-Phase zu behandeln:

    ...
connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout);
    ...

Um sicherzustellen, dass wir nur die Datagramme vom Server empfangen, verbinden wir unseren UDP-Socket mit dem Server:

    ...
socket.connectToHost(address.toString(), port);
    ...

Das Signal „ QUdpSocket::readyRead()“ ist mit dem Slot „readyRead()“ verbunden:

    ...
connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead);
    ...

Sobald eine sichere Verbindung zu einem Server hergestellt ist, sendet ein `DtlsAssociation`-Objekt mithilfe eines Timers kurze Ping-Nachrichten an den Server:

pingTimer.setInterval(5000);
connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout);

`startHandshake()` startet einen Handshake mit dem Server:

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));
}

Der Slot `readyRead()` liest ein vom Server gesendetes Datagramm:

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);

Wenn der Handshake bereits abgeschlossen ist, wird dieses Datagramm entschlüsselt:

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 {

Andernfalls versuchen wir, den Handshake fortzusetzen:

    if (!crypto.doHandshake(&socket, dgram)) {
        emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString()));
        return;
    }

Wenn der Handshake abgeschlossen ist, senden wir unsere erste Ping-Nachricht:

    if (crypto.isConnectionEncrypted()) {
        emit infoMessage(tr("%1: encrypted connection established!").arg(name));
        pingTimer.start();
        pingTimeout();
    } else {

Der Slot pskRequired() stellt den während der Handshake-Phase benötigten Pre-Shared Key (PSK) bereit:

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"));
}

Hinweis: Der Kürzehalber ist die Definition von `pskRequired()` stark vereinfacht. Die Dokumentation zur Klasse „ QSslPreSharedKeyAuthenticator “ erklärt ausführlich, wie dieser Slot korrekt implementiert werden kann.

`pingTimeout()` sendet eine verschlüsselte Nachricht an den Server:

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;
}

Während der Handshake-Phase muss der Client mögliche Timeouts behandeln, die aufgrund von Paketverlusten auftreten können. Der Slot `handshakeTimeout()` sendet die Handshake-Nachrichten erneut:

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()));
}

Bevor eine Client-Verbindung beendet wird, muss deren DTLS-Verbindung geschlossen werden:

DtlsAssociation::~DtlsAssociation()
{
    if (crypto.isConnectionEncrypted())
        crypto.shutdown(&socket);
}

Fehlermeldungen, Informationsmeldungen und entschlüsselte Antworten von Servern werden über die Benutzeroberfläche angezeigt:

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));
}

Beispielprojekt @ code.qt.io

© 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.