DTLS 클라이언트
이 예시는 클라이언트 측 DTLS 연결을 구현하는 방법을 보여줍니다.
참고: DTLS 클라이언트 예제는 DTLS 서버 예제와 함께 실행하도록 되어 있습니다.
예제 DTLS 클라이언트는 하나 또는 여러 개의 DTLS 서버에 여러 개의 DTLS 연결을 설정할 수 있습니다. 클라이언트 측 DTLS 연결은 DtlsAssociation 클래스에 의해 구현됩니다. 이 클래스는 데이터그램 읽기 및 쓰기에는 QUdpSocket, 암호화에는 QDtls 을 사용합니다:
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) };
생성자는 새 DTLS 연결에 대한 최소 TLS 구성을 설정하고 서버의 주소와 포트를 설정합니다:
... auto configuration = QSslConfiguration::defaultDtlsConfiguration(); configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); ...
QDtls::handshakeTimeout() 신호는 핸드셰이크 단계에서 패킷 손실 및 재전송을 처리하기 위해 handleTimeout() 슬롯에 연결됩니다:
... connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); ...
서버로부터 데이터그램만 수신하기 위해 UDP 소켓을 서버에 연결합니다:
... socket.connectToHost(address.toString(), port); ...
QUdpSocket::readyRead() 신호는 readyRead() 슬롯에 연결됩니다:
... connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); ...
서버에 대한 보안 연결이 설정되면 DtlsAssociation 객체가 타이머를 사용하여 서버에 짧은 핑 메시지를 보냅니다:
pingTimer.setInterval(5000); connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout);
startHandshake()는 서버와의 핸드셰이크를 시작합니다:
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)); }
readyRead() 슬롯은 서버가 보낸 데이터그램을 읽습니다:
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);
핸드셰이크가 이미 완료되었다면 이 데이터그램은 해독됩니다:
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 {
그렇지 않으면 핸드셰이크를 계속 시도합니다:
if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; }
핸드셰이크가 완료되면 첫 번째 핑 메시지를 보냅니다:
if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); pingTimer.start(); pingTimeout(); } else {
pskRequired() 슬롯은 핸드셰이크 단계에서 필요한 PSK(사전 공유 키)를 제공합니다:
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")); }
참고: 간결성을 위해 pskRequired()의 정의는 지나치게 단순화되어 있습니다. QSslPreSharedKeyAuthenticator 클래스 설명서에 이 슬롯을 올바르게 구현하는 방법이 자세히 설명되어 있습니다.
pingTimeout()은 암호화된 메시지를 서버로 보냅니다:
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; }
핸드셰이크 단계에서 클라이언트는 패킷 손실로 인해 발생할 수 있는 시간 초과를 처리해야 합니다. handshakeTimeout() 슬롯은 핸드셰이크 메시지를 재전송합니다:
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())); }
클라이언트 연결이 끊어지기 전에 해당 DTLS 연결을 종료해야 합니다:
DtlsAssociation::~DtlsAssociation() { if (crypto.isConnectionEncrypted()) crypto.shutdown(&socket); }
오류 메시지, 정보 메시지, 서버의 암호 해독된 응답이 UI에 표시됩니다:
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)); }
© 2025 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.