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解析

Qt文档阅读笔记-QtConcurrent Progress Dialog Example解析

Qt文档阅读笔记-staticMetaObject解析与实例