
>>>QUdpSocket 是 Qt 框架中用于支持用户数据报协议(UDP)网络通信的类。它允许你在网络上发送和接收数据报,适用于需要高效传输数据而不要求可靠性的应用场景,例如实时视频、语音通话等。
>>>QDtls 是 Qt 框架中用于实现 Datagram Transport Layer Security (DTLS) 的类,DTLS 是基于 UDP 的安全协议,常用于保护实时数据传输,如音频或视频流。
>>>QSharedPointer 是 Qt 框架中提供的一个智能指针类,用于管理动态分配的对象。在 C++ 中,使用智能指针可以避免内存泄漏和资源管理问题。QSharedPointer 实现了引用计数,当最后一个引用被销毁时,所管理的对象会被自动删除。
>>>QHostInfo 是 Qt 框架中用于获取主机信息的类,主要用于处理主机名和 IP 地址的解析。它可以方便地获取与主机相关的信息,包括主机名、别名及其对应的 IP 地址列表等。
>>>QIntValidator 是 Qt 框架中的一个输入验证器,用于限制输入框只能输入整数值。它可以用于 QLineEdit 或其他输入组件,以确保用户输入符合设定的整数范围。
>>>addressdialog.h
#ifndef ADDRESSDIALOG_H // 如果没有定义 ADDRESSDIALOG_H#define ADDRESSDIALOG_H // 定义 ADDRESSDIALOG_H,避免重复包含#include <QDialog> // 包含 QDialog 头文件,用于创建对话框QT_BEGIN_NAMESPACE // 开始 Qt 命名空间namespace Ui { // 创建 Ui 命名空间class AddressDialog; // 前向声明 AddressDialog 类}QT_END_NAMESPACE // 结束 Qt 命名空间class AddressDialog : public QDialog // 定义 AddressDialog 类,继承自 QDialog{ Q_OBJECT // 宏,启用 Qt 的信号和槽机制public: explicit AddressDialog(QWidget *parent = nullptr); // 构造函数,接受一个 QWidget 指针作为父对象 ~AddressDialog(); // 析构函数 QString remoteName() const; // 获取远程名称的函数声明 quint16 remotePort() const; // 获取远程端口的函数声明private: void setupHostSelector(); // 初始化主机选择器的私有函数声明 void setupPortSelector(); // 初始化端口选择器的私有函数声明 Ui::AddressDialog *ui = nullptr; // 指向 Ui::AddressDialog 的指针,初始化为 nullptr};#endif // ADDRESSDIALOG_H // 结束条件编译指令,确保避免重复定义>>>
#include "addressdialog.h" // 包含自定义的 AddressDialog 头文件#include "ui_addressdialog.h" // 包含自动生成的 UI 界面头文件#include <QtCore> // 包含 Qt 核心模块#include <QtNetwork> // 包含 Qt 网络模块#include <QtWidgets> // 包含 Qt 小部件模块#include <limits> // 包含上限和下限的头文件AddressDialog::AddressDialog(QWidget *parent) // 构造函数,接受一个 QWidget 指针作为父对象 : QDialog(parent), // 初始化父类 QDialog ui(new Ui::AddressDialog) // 初始化 ui 指针{ ui->setupUi(this); // 设置用户界面 setupHostSelector(); // 调用函数设置主机选择器 setupPortSelector(); // 调用函数设置端口选择器}AddressDialog::~AddressDialog() // 析构函数{ delete ui; // 删除 ui 指针以释放内存}QString AddressDialog::remoteName() const // 获取远程名称的函数{ if (ui->addressSelector->count()) // 如果地址选择器有项 return ui->addressSelector->currentText(); // 返回当前选中的文本 return {}; // 否则返回一个空字符串}quint16 AddressDialog::remotePort() const // 获取远程端口的函数{ return quint16(ui->portSelector->text().toUInt()); // 将端口选择器的文本转换为无符号整型}void AddressDialog::setupHostSelector() // 设置主机选择器的函数{ QString name(QHostInfo::localHostName()); // 获取本地主机名 if (!name.isEmpty()) { // 如果主机名不为空 ui->addressSelector->addItem(name); // 将主机名添加到地址选择器 const QString domain = QHostInfo::localDomainName(); // 获取本地域名 if (!domain.isEmpty()) // 如果域名不为空 ui->addressSelector->addItem(name + QChar('.') + domain); // 添加主机名和域名的组合 } if (name != QStringLiteral("localhost")) // 如果主机名不是 "localhost" ui->addressSelector->addItem(QStringLiteral("localhost")); // 添加 "localhost" const QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses(); // 获取所有 IP 地址 for (const QHostAddress &ipAddress : ipAddressesList) { // 遍历 IP 地址列表 if (!ipAddress.isLoopback()) // 如果 IP 地址不是回环地址 ui->addressSelector->addItem(ipAddress.toString()); // 添加 IP 地址到选择器 } ui->addressSelector->insertSeparator(ui->addressSelector->count()); // 在选择器中插入分隔符 for (const QHostAddress &ipAddress : ipAddressesList) { // 再次遍历 IP 地址列表 if (ipAddress.isLoopback()) // 如果是回环地址 ui->addressSelector->addItem(ipAddress.toString()); // 添加回环地址到选择器 }}void AddressDialog::setupPortSelector() // 设置端口选择器的函数{ // 设置端口选择器的验证器,范围为 0 到 quint16 的最大值 ui->portSelector->setValidator(new QIntValidator(0, std::numeric_limits<quint16>::max(), ui->portSelector)); ui->portSelector->setText(QStringLiteral("22334")); // 将端口选择器的文本设置为默认值 "22334"}>>>
#ifndef ASSOCIATION_H // 如果没有定义 ASSOCIATION_H#define ASSOCIATION_H // 定义 ASSOCIATION_H,避免重复包含#include <QtNetwork> // 包含 Qt 网络模块的头文件#include <QtCore> // 包含 Qt 核心模块的头文件//! [0]class DtlsAssociation : public QObject // 定义 DtlsAssociation 类,继承自 QObject{ Q_OBJECT // 宏,启用 Qt 的信号和槽机制public: // 构造函数,接受一个 QHostAddress、一个端口号和一个连接名称 DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); ~DtlsAssociation(); // 析构函数 void startHandshake(); // 启动 TLS 握手的公共函数声明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(); // UDP 套接字连接的槽函数 void readyRead(); // 数据可读的槽函数 void handshakeTimeout(); // 握手超时的槽函数 void pskRequired(QSslPreSharedKeyAuthenticator *auth); // 需要预共享密钥的槽函数 void pingTimeout(); // Ping 超时的槽函数private: QString name; // 连接名称 QUdpSocket socket; // UDP 套接字 QDtls crypto; // DTLs 加密对象 QTimer pingTimer; // Ping 定时器 unsigned ping = 0; // Ping 计数器 Q_DISABLE_COPY(DtlsAssociation) // 禁用拷贝构造函数和拷贝赋值运算符,以避免意外复制};//! [0]#endif // ASSOCIATION_H // 结束条件编译指令,确保避免重复定义>>>
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* #include "association.h" // 包含自定义的 association 头文件DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName) // 构造函数,接受地址、端口和连接名称 : name(connectionName), // 初始化名称 crypto(QSslSocket::SslClientMode) // 初始化加密对象为客户端模式{ //! [1] auto configuration = QSslConfiguration::defaultDtlsConfiguration(); // 获取默认的 DTLS 配置 configuration.setPeerVerifyMode(QSslSocket::VerifyNone); // 设置对等体验证模式为不验证 crypto.setPeer(address, port); // 设置对等体地址和端口 crypto.setDtlsConfiguration(configuration); // 设置 DTLS 配置 //! [1] //! [2] connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); // 连接握手超时信号 //! [2] connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); // 连接需要预共享密钥的信号 //! [3] socket.connectToHost(address.toString(), port); // 连接到指定地址和端口 //! [3] //! [13] connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); // 连接 UDP 套接字准备读信号 //! [13] //! [4] pingTimer.setInterval(5000); // 设置 Ping 定时器间隔为5000毫秒 connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); // 连接 Ping 超时信号 //! [4]}//! [12]DtlsAssociation::~DtlsAssociation() // 析构函数{ if (crypto.isConnectionEncrypted()) // 如果连接是加密的 crypto.shutdown(&socket); // 关闭加密连接}//! [12]//! [5]void DtlsAssociation::startHandshake() // 启动握手的函数{ if (socket.state() != QAbstractSocket::ConnectedState) { // 如果套接字状态不是已连接状态 emit infoMessage(tr("%1: connecting UDP socket first ...").arg(name)); // 发送信息消息,提示先连接 UDP 套接字 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)); // 发送信息消息,提示开始握手}//! [5]void DtlsAssociation::udpSocketConnected() // UDP 套接字连接后的处理函数{ emit infoMessage(tr("%1: UDP socket is now in ConnectedState, continue with handshake ...").arg(name)); // 发送信息消息,提示 UDP 套接字已连接,继续握手 startHandshake(); // 启动握手}void DtlsAssociation::readyRead() // 准备读取数据的函数{ if (socket.pendingDatagramSize() <= 0) { // 如果待处理的数据报大小小于等于0 emit warningMessage(tr("%1: spurious read notification?").arg(name)); // 发送警告消息,提示虚假的读取通知 return; // 返回 } //! [6] QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized); // 创建一个数据报数组 const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size()); // 读取数据报 if (bytesRead <= 0) { // 如果读取的字节小于等于0 emit warningMessage(tr("%1: spurious read notification?").arg(name)); // 发送警告消息,提示虚假的读取通知 return; // 返回 } dgram.resize(bytesRead); // 调整数据报大小 //! [6] //! [7] 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(); // 停止 Ping 定时器 return; // 返回 } emit warningMessage(tr("%1: zero-length datagram received?").arg(name)); // 发送警告消息,提示接收到零长度数据报 } else { //! [7] //! [8] if (!crypto.doHandshake(&socket, dgram)) { // 如果握手失败 emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); // 发送错误消息,提示握手错误 return; // 返回 } //! [8] //! [9] if (crypto.isConnectionEncrypted()) { // 如果连接是加密的 emit infoMessage(tr("%1: encrypted connection established!").arg(name)); // 发送信息消息,提示加密连接已建立 pingTimer.start(); // 启动 Ping 定时器 pingTimeout(); // 处理 Ping 超时 } else { //! [9] emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); // 发送信息消息,提示继续握手 } }}//! [11]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())); // 发送错误消息,提示重新传输失败}//! [11]//! [14]void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) // 需要预共享密钥的函数{ Q_ASSERT(auth); // 确保 auth 不是 nullptr emit infoMessage(tr("%1: providing pre-shared key ...").arg(name)); // 发送信息消息,提示提供预共享密钥 auth->setIdentity(name.toLatin1()); // 设置身份为连接名称 auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); // 设置预共享密钥}//! [14]//! [10]void DtlsAssociation::pingTimeout() // Ping 超时的函数{ static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); // 定义 Ping 消息模板 const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); // 发送加密的 Ping 消息 if (written <= 0) { // 如果发送失败 emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); // 发送错误消息,提示发送 Ping 失败 pingTimer.stop(); // 停止 Ping 定时器 return; // 返回 } ++ping; // 增加 Ping 计数}//! [10]
*/>>>
#ifndef MAINWINDOW_H // 如果没有定义 MAINWINDOW_H#define MAINWINDOW_H // 定义 MAINWINDOW_H,避免重复包含#include <QMainWindow> // 包含 QMainWindow 头文件,用于创建主窗口#include <QSharedPointer> // 包含 QSharedPointer 头文件,用于使用共享指针#include <QList> // 包含 QList 头文件,用于使用 QList 容器QT_BEGIN_NAMESPACE // 开始 Qt 命名空间namespace Ui { // 创建 Ui 命名空间class MainWindow; // 前向声明 MainWindow 类}QT_END_NAMESPACE // 结束 Qt 命名空间class QHostAddress; // 前向声明 QHostAddress 类class QHostInfo; // 前向声明 QHostInfo 类class DtlsAssociation; // 前向声明 DtlsAssociation 类class MainWindow : public QMainWindow // 定义 MainWindow 类,继承自 QMainWindow{ Q_OBJECT // 宏,启用 Qt 的信号和槽机制public: explicit MainWindow(QWidget *parent = nullptr); // 构造函数,接受一个 QWidget 指针作为父对象 ~MainWindow(); // 析构函数private slots: // 槽函数部分 void addErrorMessage(const QString &message); // 添加错误信息的槽函数 void addWarningMessage(const QString &message); // 添加警告信息的槽函数 void addInfoMessage(const QString &message); // 添加信息的槽函数 void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText); // 添加服务器响应的槽函数 void on_connectButton_clicked(); // 连接按钮点击的槽函数 void on_shutdownButton_clicked(); // 关闭按钮点击的槽函数 void lookupFinished(const QHostInfo &hostInfo); // 查询完成的槽函数private: void updateUi(); // 更新用户界面的私有函数声明 void startNewConnection(const QHostAddress &address); // 启动新连接的私有函数声明 Ui::MainWindow *ui = nullptr; // 指向 Ui::MainWindow 的指针,初始化为 nullptr using AssocPtr = QSharedPointer<DtlsAssociation>; // 定义共享指针类型别名 QList<AssocPtr> connections; // 连接的列表,存储 DtlsAssociation 的共享指针 QString nameTemplate; // 名称模板 unsigned nextId = 0; // 下一个 ID quint16 port = 0; // 端口号 int lookupId = -1; // 查询 ID,初始化为 -1};#endif // MAINWINDOW_H // 结束条件编译指令,确保避免重复定义>>>
#include <QtCore> // 包含 Qt 核心模块#include <QtNetwork> // 包含 Qt 网络模块#include "addressdialog.h" // 包含地址对话框头文件#include "association.h" // 包含关联头文件#include "mainwindow.h" // 包含主窗口头文件#include "ui_mainwindow.h" // 包含用户界面头文件#include <utility> // 包含工具模块,用于 std::moveMainWindow::MainWindow(QWidget *parent) // 构造函数,接受一个 QWidget 指针作为父对象 : QMainWindow(parent), // 初始化父类 QMainWindow ui(new Ui::MainWindow), // 初始化 ui 指针 nameTemplate(QStringLiteral("Alice (clone number %1)")) // 初始化名称模板{ ui->setupUi(this); // 设置用户界面 updateUi(); // 更新用户界面}MainWindow::~MainWindow() // 析构函数{ delete ui; // 删除 ui 指针以释放内存}//! [0]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)); // 创建响应消息的 HTML ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); // 插入服务器响应消息}//! [0]void MainWindow::on_connectButton_clicked() // 连接按钮点击的槽函数{ if (lookupId != -1) { // 如果正在进行主机查询 QHostInfo::abortHostLookup(lookupId); // 取消主机查询 lookupId = -1; // 重置查询 ID port = 0; // 重置端口 updateUi(); // 更新用户界面 return; // 返回 } AddressDialog dialog; // 创建地址对话框实例 if (dialog.exec() != QDialog::Accepted) // 显示对话框,如果未被接受则返回 return; const QString hostName = dialog.remoteName(); // 获取远程主机名 if (hostName.isEmpty()) // 如果主机名为空 return addWarningMessage(tr("Host name or address required to connect")); // 添加警告消息,提示需要主机名或地址 port = dialog.remotePort(); // 获取远程端口 QHostAddress remoteAddress; // 创建远程地址对象 if (remoteAddress.setAddress(hostName)) // 如果能将主机名转换为地址 return startNewConnection(remoteAddress); // 启动新连接 addInfoMessage(tr("Looking up the host ...")); // 添加信息消息,提示正在查找主机 lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo))); // 开始异步主机查找 updateUi(); // 更新用户界面}void MainWindow::updateUi() // 更新用户界面的函数{ ui->connectButton->setText(lookupId == -1 ? tr("Connect ...") : tr("Cancel lookup")); // 根据查询 ID 设置按钮文本 ui->shutdownButton->setEnabled(connections.size() != 0); // 启用/禁用关闭按钮}void MainWindow::lookupFinished(const QHostInfo &hostInfo) // 主机查找完成的槽函数{ if (hostInfo.lookupId() != lookupId) // 如果返回的查找 ID 与当前的查找 ID 不匹配 return; // 返回 lookupId = -1; // 重置查找 ID updateUi(); // 更新用户界面 if (hostInfo.error() != QHostInfo::NoError) { // 如果查找发生错误 addErrorMessage(hostInfo.errorString()); // 添加错误消息 return; // 返回 } const QList<QHostAddress> foundAddresses = hostInfo.addresses(); // 获取查找到的地址列表 if (foundAddresses.empty()) { // 如果地址列表为空 addWarningMessage(tr("Host not found")); // 添加警告消息,提示未找到主机 return; // 返回 } const auto remoteAddress = foundAddresses.at(0); // 获取第一个找到的地址 addInfoMessage(tr("Connecting to: %1").arg(remoteAddress.toString())); // 添加信息消息,提示正在连接 startNewConnection(remoteAddress); // 启动新连接}void MainWindow::startNewConnection(const QHostAddress &address) // 启动新连接的函数{ AssocPtr newConnection(new DtlsAssociation(address, port, nameTemplate.arg(nextId))); // 创建新的 DtlsAssociation 实例 connect(newConnection.data(), &DtlsAssociation::errorMessage, this, &MainWindow::addErrorMessage); // 连接错误消息信号 connect(newConnection.data(), &DtlsAssociation::warningMessage, this, &MainWindow::addWarningMessage); // 连接警告消息信号 connect(newConnection.data(), &DtlsAssociation::infoMessage, this, &MainWindow::addInfoMessage); // 连接信息消息信号 connect(newConnection.data(), &DtlsAssociation::serverResponse, this, &MainWindow::addServerResponse); // 连接服务器响应信号 connections.push_back(std::move(newConnection)); // 将新连接添加到连接列表 connections.back()->startHandshake(); // 启动握手 updateUi(); // 更新用户界面 ++nextId; // 增加下一个 ID}void MainWindow::on_shutdownButton_clicked() // 关闭按钮点击的槽函数{ connections.clear(); // 清空连接列表 updateUi(); // 更新用户界面}>>>

通过网盘分享的文件:secureudpclient 链接: https://pan.baidu.com/s/1txCWIo7-WhM-CjVkp_aDdg?pwd=13j9 提取码: 13j9 【一定要转存】
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。