Qt网络编程:DTLS 服务端和客户端通信(官方Demo)

一、服务端

    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);

相关博文:

QDtls

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值