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