前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Qt官方示例-DTLS服务器

Qt官方示例-DTLS服务器

作者头像
Qt君
发布于 2023-03-17 05:49:00
发布于 2023-03-17 05:49:00
1.5K00
代码可运行
举报
文章被收录于专栏:跟Qt君学编程跟Qt君学编程
运行总次数:0
代码可运行

❝该示例演示如何实现简单的DTLS服务器。❞

DTLS 是指 Datagram Transport Level Security,即数据报安全传输协议。DTLS作为UDP版本的TLS。

「注意:DTLS服务器示例旨在与DTLS客户端示例一起运行。」

  该服务器由DtlsServer类实现。它使用QUdpSocket,QDtlsClientVerifier和QDtls来测试每个客户端的可达性,完成握手以及读取和写入加密的消息。

代码语言:javascript
代码运行次数:0
运行
复制
class DtlsServer : public QObject
{
    Q_OBJECT

public:
    DtlsServer();
    ~DtlsServer();

    bool listen(const QHostAddress &address, quint16 port);
    bool isListening() const;
    void close();

signals:
    void errorMessage(const QString &message);
    void warningMessage(const QString &message);
    void infoMessage(const QString &message);

    void datagramReceived(const QString &peerInfo, const QByteArray &cipherText,
                          const QByteArray &plainText);

private slots:
    void readyRead();
    void pskRequired(QSslPreSharedKeyAuthenticator *auth);

private:
    void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort,
                             const QByteArray &clientHello);

    void doHandshake(QDtls *newConnection, const QByteArray &clientHello);
    void decryptDatagram(QDtls *connection, const QByteArray &clientMessage);
    void shutdown();

    bool listening = false;
    QUdpSocket serverSocket;

    QSslConfiguration serverConfiguration;
    QDtlsClientVerifier cookieSender;
    std::vector<std::unique_ptr<QDtls>> knownClients;

    Q_DISABLE_COPY(DtlsServer)
};

  构造函数将QUdpSocket::readyRead()信号连接到其readyRead()槽函数中,并设置所需的最少TLS配置:

代码语言:javascript
代码运行次数:0
运行
复制
DtlsServer::DtlsServer()
{
    connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead);

    serverConfiguration = QSslConfiguration::defaultDtlsConfiguration();
    serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server");
    serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
}

「注意:服务器未使用证书,并且依赖于预共享密钥(PSK)握手。」

listen函数绑定QUdpSocket:

代码语言:javascript
代码运行次数:0
运行
复制
bool DtlsServer::listen(const QHostAddress &address, quint16 port)
{
    if (address != serverSocket.localAddress() || port != serverSocket.localPort()) {
        shutdown();
        listening = serverSocket.bind(address, port);
        if (!listening)
            emit errorMessage(serverSocket.errorString());
    } else {
        listening = true;
    }

    return listening;
}

readyRead槽函数处理传入的数据报文:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
const qint64 bytesToRead = serverSocket.pendingDatagramSize();
if (bytesToRead <= 0) {
    emit warningMessage(tr("A spurious read notification"));
    return;
}

QByteArray dgram(bytesToRead, Qt::Uninitialized);
QHostAddress peerAddress;
quint16 peerPort = 0;
const qint64 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(),
                                                   &peerAddress, &peerPort);
if (bytesRead <= 0) {
    emit warningMessage(tr("Failed to read a datagram: ") + serverSocket.errorString());
    return;
}

dgram.resize(bytesRead);
    ...

  提取地址和端口号之后,服务器首先测试它是否是来自已知对等方的数据报文:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
if (peerAddress.isNull() || !peerPort) {
    emit warningMessage(tr("Failed to extract peer info (address, port)"));
    return;
}

const auto client = std::find_if(knownClients.begin(), knownClients.end(),
                                 [&](const std::unique_ptr<QDtls> &connection){
    return connection->peerAddress() == peerAddress
           && connection->peerPort() == peerPort;
});
    ...

  如果它是新的未知地址和端口,则数据报将作为潜在的ClientHello消息处理,由DTLS客户端发送:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
if (client == knownClients.end())
    return handleNewConnection(peerAddress, peerPort, dgram);
    ...

  如果它是已知的DTLS客户端,则服务器要么解密数据报文:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
if ((*client)->isConnectionEncrypted()) {
    decryptDatagram(client->get(), dgram);
    if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError)
        knownClients.erase(client);
    return;
}
    ...

  或继续与此对等方握手:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
doHandshake(client->get(), dgram);
    ...

handleNewConnection()验证它是可访问的DTLS客户端,或发送HelloVerifyRequest:

代码语言:javascript
代码运行次数:0
运行
复制
void DtlsServer::handleNewConnection(const QHostAddress &peerAddress,
                                     quint16 peerPort, const QByteArray &clientHello)
{
    if (!listening)
        return;

    const QString peerInfo = peer_info(peerAddress, peerPort);
    if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) {
        emit infoMessage(peerInfo + tr(": verified, starting a handshake"));
    ...

  如果新客户端已被验证为可访问的DTLS客户端,则服务器将创建并配置新的QDtls对象,并启动服务器端握手:

代码语言:javascript
代码运行次数:0
运行
复制
    ...
    std::unique_ptr<QDtls> newConnection{new QDtls{QSslSocket::SslServerMode}};
    newConnection->setDtlsConfiguration(serverConfiguration);
    newConnection->setPeer(peerAddress, peerPort);
    newConnection->connect(newConnection.get(), &QDtls::pskRequired,
                           this, &DtlsServer::pskRequired);
    knownClients.push_back(std::move(newConnection));
    doHandshake(knownClients.back().get(), clientHello);
    ...

doHandshake()进入握手阶段:

代码语言:javascript
代码运行次数:0
运行
复制
void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello)
{
    const bool result = newConnection->doHandshake(&serverSocket, clientHello);
    if (!result) {
        emit errorMessage(newConnection->dtlsErrorString());
        return;
    }

    const QString peerInfo = peer_info(newConnection->peerAddress(),
                                       newConnection->peerPort());
    switch (newConnection->handshakeState()) {
    case QDtls::HandshakeInProgress:
        emit infoMessage(peerInfo + tr(": handshake is in progress ..."));
        break;
    case QDtls::HandshakeComplete:
        emit infoMessage(tr("Connection with %1 encrypted. %2")
                         .arg(peerInfo, connection_info(newConnection)));
        break;
    default:
        Q_UNREACHABLE();
    }
}

  在握手阶段,将发出QDtls::pskRequired()信号,而pskRequired()槽函数将提供预共享密钥:

代码语言:javascript
代码运行次数:0
运行
复制
void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth)
{
    Q_ASSERT(auth);

    emit infoMessage(tr("PSK callback, received a client's identity: '%1'")
                     .arg(QString::fromLatin1(auth->identity())));
    auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"));
}

「注意:为了简洁起见,pskRequired()的定义被简化了。QSslPreSharedKeyAuthenticator类的文档详细说明了如何正确实现此槽函数。」

  网络对等方完成握手后,将视为已建立加密的DTLS连接,并且服务器通过调用cryptoDatagram()来解密对等方发送的后续数据报。服务器还将加密的响应发送到对等方:

代码语言:javascript
代码运行次数:0
运行
复制
void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage)
{
    Q_ASSERT(connection->isConnectionEncrypted());

    const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort());
    const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage);
    if (dgram.size()) {
        emit datagramReceived(peerInfo, clientMessage, dgram);
        connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1());
    } else if (connection->dtlsError() == QDtlsError::NoError) {
        emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?"));
    } else {
        emit errorMessage(peerInfo + ": " + connection->dtlsErrorString());
    }
}

  服务器通过调用QDtls::shutdown()关闭其DTLS连接:

代码语言:javascript
代码运行次数:0
运行
复制
void DtlsServer::shutdown()
{
    for (const auto &connection : qExchange(knownClients, {}))
        connection->shutdown(&serverSocket);

    serverSocket.close();
}

  在其运行期间,服务器通过发出errorMessage()warningMessage()infoMessage()datagramReceived()信号来报告错误,信息消息和解密的数据报。这些消息由服务器的UI记录:

代码语言:javascript
代码运行次数:0
运行
复制
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>"));

void MainWindow::addErrorMessage(const QString &message)
{
    ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message));
}

void MainWindow::addWarningMessage(const QString &message)
{
    ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message));
}

void MainWindow::addInfoMessage(const QString &message)
{
    ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message));
}

void MainWindow::addClientMessage(const QString &peerInfo, const QByteArray &datagram,
                                  const QByteArray &plainText)
{
    static const QString messageColor = QStringLiteral("DarkMagenta");
    static const QString formatter = QStringLiteral("<br>---------------"
                                                    "<br>A message from %1"
                                                    "<br>DTLS datagram:<br> %2"
                                                    "<br>As plain text:<br> %3");

    const QString html = formatter.arg(peerInfo, QString::fromUtf8(datagram.toHex(' ')),
                                       QString::fromUtf8(plainText));
    ui->messages->insertHtml(colorizer.arg(messageColor, html));
}

关于更多

  • 「QtCreator软件」可以找到:
  • 或在以下「Qt安装目录」找到:
代码语言:javascript
代码运行次数:0
运行
复制
C:\Qt\{你的Qt版本}\Examples\{你的Qt版本}\network\secureudpserver
  • 「相关链接」
代码语言:javascript
代码运行次数:0
运行
复制
https://doc.qt.io/qt-5/qtnetwork-secureudpserver-example.html
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Qt君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Mysql必知必会!
数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作
网络技术联盟站
2020/09/29
2K0
Mysql必知必会!
MySQL表的增删查改
values左侧为表中属性,右侧为自定义插入的内容,左右两侧安装顺序是一一对应的,如果顺序不同就会导致类型不同而出错。
每天都要进步呀
2023/10/16
6410
MySQL表的增删查改
mysql必备语句
1,没有数据库,使用磁盘文件存储数据; 2, 层次结构模型数据库; 3,网状结构模型数据库; 4,关系结构模型数据库:使用二维表格来存储数据; 5,关系-对象模型数据库; MySQL就是关系型数据库!
cherishspring
2019/10/14
13K0
MySQL学习笔记汇总(一)——简单查询、条件查询、数据排序。
今天的分享就到这里啦!!~感谢大家的观看,希望对大家有帮助的话麻烦给个丝滑三连击。(点赞+转发+关注) 一起加油,一起努力,一起秃见成效!!
百思不得小赵
2022/12/01
1.3K0
MySQL学习笔记汇总(一)——简单查询、条件查询、数据排序。
数据库select语句详解
1)检索单个列 select ename from emp; 2) 检索多个列 select ename,job,sal from emp; 3) 检索所有列 select * from emp; 4) 去除重复 select distinct deptno from emp; 5) 别名 select ename as 姓名 from emp; 6) 伪列,即不存在的列,构建虚拟的列 select empno, 1*2 as count,‘cmj’ as name,deptno from emp; 7)虚表,及不存在的表,可以计算 select 1+1 from dual;
全栈程序员站长
2022/09/06
2.5K0
数据库select语句详解
Oracle应用实战五——SQL查询
Oracle SQL SQL学习是重点,请仔细阅读。 O Oracle 结构化查询语言(Structured Query Language)简称SQL(发音:/ˈɛs kjuː ˈɛl/ "S-Q
Java帮帮
2018/03/19
1.4K0
Oracle应用实战五——SQL查询
Oracle数据库学习笔记 (四 —— select 从入门到放弃 【上】)
基本语法 order by xxxx asc(desc) asc 升序, desc 降序
Gorit
2021/12/09
1.2K0
Oracle 中的SELECT 关键字(查询、检索)
检索单个列:select 列名 from 表名; 例:select ename from emp; 检索多个列: select [列1,列2, ... ,列N] from 表名; 例:select ename , sal from emp; 检索所有列:select * from 表名; 例:select * from emp;
星哥玩云
2022/08/18
4.6K0
oracle--单表查询
---单表的查询学习 --查询表的所有数据 select * from 表名;*代表所有 select * from emp; --查询表中指定字段的值 select 字段名1,字段名2,...from表名 select empno from emp; select empno,ename from emp; --给查询结果中的字段使用别名 --在字段名后使用关键字 字段名 as "别名" --作用:方便查看查询结果 --注意:as关键字可以省略不写,别名中没有特殊字符双引号也可以省略不写。 select empno 员工编号,ename"员工 姓名",job as 工作,mgr as "领导编号" from emp; --连接符:select 字段名||'字符'||字段名||..... from 表名 --||为sql语句的字符链接符,使用在select和from之间 --字符链接格式为 字段名||'字符'||字段名 --注意:一个拼接好的连接在结果集中是作为一个新的字段显示,可以使用别名优化字段显示。 select empno||'的姓名是'||ename as"信息",job||'哈哈'||mgr from emp; --去除重复 select distinct 字段名,字段名,...fromn 表名 ---注意:去除重复的规则是按照行进行去除的,多行数据完全相同取其一 select distinct job ,mgr from emp; --排序 --单字段排序 --select * from 表名 order by 字段名 asc 升序排序 asc可以省略不写 --select * from 表名 order by 字段名 desc 降序序排序 --多字段排序 --select * from emp order by 字段名1,字段名2... --先按照字段1排序,如果字段1的值相同,则按照字段2排序,.... select * from emp order by empno desc--单字段排序 降序 select empno,ename,job from emp order by ename asc--单字段排序 升序 select * from emp order by empno,ename--多字段排序 --字段的逻辑运算 --select关键字和from关键字之间的字段可以直接进行四则运算 --字段与字段之间也可以直接进行运算 --注意:字段值为数值类型 select * from emp select empno,ename,job,sal*2+1000,sal+comm from emp ----------------------------------------------------------------- --使用where子句查询筛选 --select 字段名,字段名,...from表名 where 筛选条件 --单筛选条件 --使用运算符进行筛选 =,>,>=,<,<=,<> 单个条件中 --注意:如果条件中的值为字符,必须使用单引号括起来 --查询所有的员工的工资信息 select empno,ename,sal+comm as 薪资 from emp --查询SMITH的个人信息 select * from emp where ename='SMITH' --查询SMITH的薪资信息,逻辑运算符= select empno,ename,sal,sal+comm from emp where ename='SMITH' --查询工资大于1000的员工信息,逻辑符> select * from emp where sal>'2000' --查询工资不等于3000的员工信息 select * from emp where sal<>3000 order by sal --练习: --查看工资等于1250的员工信息
eadela
2019/09/29
7660
MySQL查询语句
  select * from emp;  在日常工作中 不建议使用* 因为查询效率较低
用户7630333
2023/12/07
1.1K0
MySQL查询语句
Oracle数据库之限定查询和排序显示详解
范例:根据之前的查询结果发现 SMITH 的工资最低,现在希望可以取得 SMITH 的详细资料。
星哥玩云
2022/08/18
1.4K0
PLSQL 基础教程 三 查询(SELECT)
本节教程将继续介绍SQL基础知识中的SELECT相关的一些知识,包括基础语法、多表连接、去重、排序、子查询等等SELECT方面的基础知识。
全栈程序员站长
2022/08/31
4.6K0
PLSQL 基础教程 三 查询(SELECT)
常用sql查询语句
5.9 合并查询(union 并集, intersect 交集, union all 并集+交集, minus差集)
FGGIT
2024/10/15
3130
《数据库查询:解锁数据宝藏的魔法之钥》
MySQL查询是一种用于检索、筛选和分析数据的数据库操作技术。作为一个强大的关系型数据库管理系统(RDBMS),MySQL支持多种查询方法,包括使用SQL(Structured Query Language)编写的查询语句。
杨不易呀
2023/09/27
2520
《数据库查询:解锁数据宝藏的魔法之钥》
MySQL表的增删改查
引言:CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写
用户11305962
2024/10/09
3840
MySQL表的增删改查
mysql系列一
学习mysql必备工具即安装mysql客户端;mysql安装教程在网上有很多,在此处就不在仔细说明;
沁溪源
2020/09/03
1.1K0
MySQL入门学习笔记(上)
英文单词DataBase,简称DB。按照一定格式存储数据的一些文件的组合。 顾名思义:存储数据的仓库,实际上就是一堆文件。这些文件中存储了具有特定格式的数据。
啵啵鱼
2022/11/23
1.9K0
MySQL入门学习笔记(上)
Oracle数据库 sql条件查询语句与练习
a)、= 、 >、 <、 >=、 <=、 !=、 <>、 between and b)、and 、or、 not、 union、 union all、 intersect 、minus c)、null :is null、 is not null、 not is null d)、like :模糊查询 % _ escape('单个字符') f)、in 、 exists(难点) 及子查询m
wolf
2020/09/20
1.2K0
推荐学Java——数据表操作
上节内容学习了数据库 MySQL 的安装、验证、数据库管理工具、数据库的基本操作命令,还没有学习的同学可以从主页去看上一篇推送内容。
逆锋起笔
2022/01/13
2.8K0
推荐学Java——数据表操作
mysql入门
定义:操作一组数据(多行记录)返回一个结果,也叫分组函数 大多用于统计 例如:统计各部门中雇员的人数。统计各部门中最高和最低薪资
崔笑颜
2020/06/08
1.1K0
相关推荐
Mysql必知必会!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档