本文共 6205 字,大约阅读时间需要 20 分钟。
此篇博文讲解了DTLS客户端的编写
注意:DTLS客户端需要结合DTLS服务端一起跑才有效果。
这里使用DTLS客户端使用少量的连接可以和一个或多个DTLS服务端进行通信。DtlsAssociation是DTLS客户端连接类。这个类使用了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) };
构造函数中最小化配置了TLS,用于DTLS连接的创建,以及配置了IP地址和端口:
... 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); ...
使用UDPSocket连接到服务端:
... socket.connectToHost(address.toString(), port); ...
QUdpSocket::readyRead()信号与readyRead()槽函数相关联
... connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); ...
当一条加密的连接创建后,DtlsAssociation对象会发送一条简短的ping消息到服务端:
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; }
当握手完成,就发送第一个ping消息:
if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); pingTimer.start(); pingTimeout(); } else {
pskRequired()槽函数在握手期间提供了Pre-Shared Key(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")); }
上面的代码是比较简洁的,QSslPreSharedKeyAuthenticator类讲解了如何正确的设置PSK及证书相关的设置。
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("%2")); 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("---------------" "%1 received a DTLS datagram: %2" "As plain text: %3"); const QString html = formatter.arg(clientInfo, QString::fromUtf8(datagram.toHex(' ')), QString::fromUtf8(plainText)); ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); }
转载地址:https://it1995.blog.csdn.net/article/details/117701478 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!