Qt文档阅读笔记-DTLS client解析
Posted IT1995
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt文档阅读笔记-DTLS client解析相关的知识,希望对你有一定的参考价值。
此篇博文讲解了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("<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));
}
以上是关于Qt文档阅读笔记-DTLS client解析的主要内容,如果未能解决你的问题,请参考以下文章
网络协议文档阅读笔记-TLS vs DTLS | Difference between TLS and DTLS
网络协议文档阅读笔记-Introduction to DTLS(Datagram Transport Layer Security)
Qt文档阅读笔记-Simple Chat Example解析
Qt文档阅读笔记-Broadcast Sender Example解析