一、服务端
QUdpSocket serverSocket;
QSslConfiguration serverConfiguration;//ssl配置信息
QDtlsClientVerifier cookieSender;//DTLS cookie校验器
std::vector<std::unique_ptr<QDtls>> knownClientList;//储存连接
1.1、服务端配置
connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead);
serverConfiguration = QSslConfiguration::defaultDtlsConfiguration();
serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS 服务端示例");
serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
1.2、监听
bool listening = serverSocket.bind(address, port);
if (!listening)
qDebug()<<"开启监听失败:"<<serverSocket.errorString();
1.3、处理接收到的消息
void DtlsServer::readyRead()
{
const qint64 bytesToRead = serverSocket.pendingDatagramSize();
if (bytesToRead <= 0)
{
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)
{
qDebug()<<"读取数据失败: "<<serverSocket.errorString();
return;
}
dgram.resize(bytesRead);
if (peerAddress.isNull() || !peerPort)
{
qDebug()<<"无法提取对等信息(地址、端口)";
return;
}
//从已建立连接的QDtls列表中根据地址和端口查找
const auto client = std::find_if(knownClientList.begin(), knownClientList.end(),[&](const std::unique_ptr<QDtls> &connection)
{
return connection->peerAddress() == peerAddress &&
connection->peerPort() == peerPort;
});
//未找到则建立新的连接
if (client == knownClientList.end())
return handleNewConnection(peerAddress, peerPort, dgram);
if ((*client)->isConnectionEncrypted())//已完成握手的
{
decryptDatagram(client->get(), dgram);//解密数据报
if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError)//TLS 关闭警报消息
knownClientList.erase(client);//踢出列表
return;
}
doHandshake(client->get(), dgram);//尝试进行握手
}
1.4、建立新连接
void DtlsServer::handleNewConnection(const QHostAddress &peerAddress,
quint16 peerPort, const QByteArray &clientHello)
{
if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort))//使用DTLS cookie校验器进行校验客户端
{
qDebug()<< peerAddress.toString() <<" "<< peerPort <<":验证完成,开始握手"));
std::unique_ptr<QDtls> newConnection{new QDtls{QSslSocket::SslServerMode}};
newConnection->setDtlsConfiguration(serverConfiguration);
newConnection->setPeer(peerAddress, peerPort);
newConnection->connect(newConnection.get(), &QDtls::pskRequired,[](QSslPreSharedKeyAuthenticator *auth)
{
qDebug()<< "PSK 回调,收到客户端的身份: "<< QString::fromLatin1(auth->identity());
auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"));
});
knownClientList.push_back(std::move(newConnection));
doHandshake(knownClientList.back().get(), clientHello);
}
else if (cookieSender.dtlsError() != QDtlsError::NoError)
{
qDebug()<< "DTLS 错误: " << cookieSender.dtlsErrorString());
}
else
{
qDebug()<< peerAddress.toString() <<" "<< peerPort <<":尚未校验"));
}
}
1.5、解密数据报
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())
{
qDebug()<<connection->peerAddress()<<" "<<connection->peerPort()<<"确认收到数据";
connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(connection->peerAddress()).arg(connection->peerPort()).toLatin1());//告诉客户端收到数据了
}
else if (connection->dtlsError() == QDtlsError::NoError)
{
qDebug()<<"收到0字节的数据";
}
else
{
qDebug()<<"错误信息:"<<connection->dtlsErrorString();
}
}
1.6、握手操作
void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello)
{
if (!newConnection->doHandshake(&serverSocket, clientHello))
{
qDebug()<<"握手失败:"<<newConnection->dtlsErrorString();
return;
}
const QString peerInfo = peer_info(newConnection->peerAddress(),
newConnection->peerPort());
switch (newConnection->handshakeState())
{
case QDtls::HandshakeInProgress:
qDebug()<<newConnection->peerAddress() << newConnection->peerPort() <<": 握手正在进行中 ...";
break;
case QDtls::HandshakeComplete:
qDebug()<<"与 "<<newConnection->peerAddress() << newConnection->peerPort() <<"的连接已加密";
qDebug()<< "会话密码:"<<newConnection->sessionCipher().name();
qDebug()<< "会话协议:"<<newConnection->sessionProtocol();
break;
default:
Q_UNREACHABLE();
}
}
1.7、关闭服务端
void DtlsServer::shutdown()
{
for (const auto &connection : qExchange(knownClientList, {}))
connection->shutdown(&serverSocket);
serverSocket.close();
}
二、客户端
QString connectionName;
QUdpSocket socket;
QDtls crypto;
2.1、客户端配置
auto configuration = QSslConfiguration::defaultDtlsConfiguration();
configuration.setPeerVerifyMode(QSslSocket::VerifyNone);
crypto.setPeer(address, port);
crypto.setDtlsConfiguration(configuration);
connect(&crypto, &QDtls::handshakeTimeout, []
{
qDebug()<<"握手超时,尝试重新传输";
if (!crypto.handleTimeout(&socket))
qDebug()<<"重传失败"<<crypto.dtlsErrorString();
});
connect(&crypto, &QDtls::pskRequired,[this](QSslPreSharedKeyAuthenticator *auth)
{
qDebug()<<"提供预共享密钥:"<<connectionName;
auth->setIdentity(connectionName.toLatin1());
auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"));
});
socket.connectToHost(address.toString(), port);//连接服务端
connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead);
2.2、握手操作
void DtlsAssociation::startHandshake()
{
if (socket.state() != QAbstractSocket::ConnectedState)
{
connect(&socket, &QAbstractSocket::connected, this, &DtlsAssociation::udpSocketConnected);
return;
}
if (!crypto.doHandshake(&socket))
qDebug()<<"未能开始握手"<<crypto.dtlsErrorString();
else
qDebug()<<"开始与"<<connectionName<<"握手";
}
void DtlsAssociation::udpSocketConnected()
{
qDebug()<<"UDP socket 现在处于 ConnectedState状态,将继续握手 ...";
startHandshake();
}
2.3、处理数据
void DtlsAssociation::readyRead()
{
QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized);
const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size());
if (bytesRead <= 0)
{
return;
}
dgram.resize(bytesRead);
if (crypto.isConnectionEncrypted())
{
const QByteArray plainText = crypto.decryptDatagram(&socket, dgram);
if (plainText.size())
{
qDebug()<<"收到信息:"<< plainText;
return;
}
if (crypto.dtlsError() == QDtlsError::RemoteClosedConnectionError)
{
qDebug()<<"收到断开警报";
socket.close();
return;
}
}
else
{
if (!crypto.doHandshake(&socket, dgram))//握手出错
{
qDebug()<<"握手错误:"<<crypto.dtlsErrorString();
return;
}
if (crypto.isConnectionEncrypted())
{
qDebug()<<"与"<<connectionName<<"建立加密连接!";
}
else
{
qDebug()<<"继续与"<<connectionName<<"握手";
}
}
}
2.4、给服务端发送信息
static const QString message = "this is my data";
const qint64 written = crypto.writeDatagramEncrypted(&socket, message.toLatin1());
if (written <= 0)
{
qDebug()<<"发送消息失败"<< crypto.dtlsErrorString();
}
2.5、断开连接
if (crypto.isConnectionEncrypted())
crypto.shutdown(&socket);
相关博文: