QSslSocket 在等待数据时超时(但 QTcpSocket 不会)

QSslSocket times out when waiting for data (but QTcpSocket does not)

本文关键字:QTcpSocket 不会 超时 在等待 数据 QSslSocket      更新时间:2023-10-16

这是一对简单的客户端和服务器应用程序,它们尝试通过QTcpSocket或QSslSocket进行通信,并相互发送几个字节的数据。它似乎可以通过TCP工作,但是当我尝试使用SSL时,服务器端的读取总是超时。请告诉我我做错了什么。

这是我的完整代码:

客户:

#include <QTcpSocket>
#include <QSslSocket>
#include <QTimer>
#include <QApplication>
#define dumpvar(x) qDebug()<<#x<<'='<<x
// reads exactly len bytes from d into data,
// blocks until data is available
// or throws std::runtime_error on error or timeout
// timeout_msec = -1 means no timeout
void readWrapper(QIODevice &d, void *data, qint64 len, int timeout_msec)
{
    QTimer t;
    t.setSingleShot(true);
    // note: inactive timer's remainingTime() returns -1
    if (timeout_msec >= 0) t.start(timeout_msec);
    qint64 bytesRead = 0;
    auto dataPtr = (char*)data;
    while (bytesRead < len){
        auto bytesNeeded = len - bytesRead;
        auto ret = d.read(dataPtr, bytesNeeded);
        if (ret == -1){
            throw std::runtime_error(d.errorString().toStdString());
        }
        qDebug()<<QString("Attempted to read %1 bytes, got %2 bytes")
                  .arg(bytesNeeded)
                  .arg(ret);
        if (ret == 0) {
            qDebug("Calling waitForReadyRead");
            bool ret = d.waitForReadyRead(t.remainingTime());
            if (!ret){
                qDebug()<<"waitForReadyRead failed";
                throw std::runtime_error(d.errorString().toStdString());
            }
        }
        else {
            bytesRead += ret;
            dataPtr += ret;
        }
        if (t.remainingTime() == 0)
            throw std::runtime_error("blockingRead timeout");
    }
}
QByteArray readWrapper(QIODevice &d, qint64 len, int timeout_msec = 10000)
{
    QByteArray ret;
    ret.resize(len);
    readWrapper(d, ret.data(), len, timeout_msec);
    return ret;
}
void writeWrapper(QIODevice &d, void *data, qint64 len)
{
    auto ret = d.write((char*)data, len);
    if (ret < len){
        throw std::runtime_error(QString("error writing data: %1")
                                 .arg(d.errorString())
                                 .toStdString());
    }
}
void writeWrapper(QIODevice &d, const QByteArray &data)
{
    writeWrapper(d, (void*)data.data(), data.size());
}
using namespace std;
void handleConnection(QAbstractSocket& s)
{
    QByteArray data = "test c";
    qDebug()<<"writing data";
    writeWrapper(s, data);
    qDebug()<<"reading data";
    auto readData = readWrapper(s, 6);
    dumpvar(readData);
    s.disconnectFromHost();
    s.waitForDisconnected();
}
void tcpclient()
{
    QTcpSocket s;
    s.connectToHost("localhost", 1234);
    if(!s.waitForConnected(-1)){
        qDebug()<<s.errorString();
        return;
    }
    qDebug()<<"client connected";
    handleConnection(s);
}
void sslclient()
{
    QSslSocket s;
    auto cert = QSslCertificate::fromPath("/home/piotrek/cert.pem");
    Q_ASSERT(!cert.isEmpty());
    s.setCaCertificates({cert});
    s.ignoreSslErrors({{QSslError::HostNameMismatch, cert[0]}});
    s.connectToHostEncrypted("localhost", 1234);
    if (!s.waitForEncrypted(10000)){
        dumpvar(s.errorString());
        dumpvar(s.sslErrors());
        return;
    }
    qDebug()<<"client connected";
    handleConnection(s);
}

int main(int argc, char** argv)
{
    QCoreApplication a(argc, argv);
//  tcpclient();
    sslclient();
}

服务器:

#include <QTcpSocket>
#include <QSslSocket>
#include <QTimer>
#include <QCoreApplication>
#include <QTcpServer>
#include <QSslKey>
#define dumpvar(x) qDebug()<<#x<<'='<<x
// reads exactly len bytes from d into data,
// blocks until data is available
// or throws std::runtime_error on error or timeout
// timeout_msec = -1 means no timeout
void readWrapper(QIODevice &d, void *data, qint64 len, int timeout_msec)
{
    QTimer t;
    t.setSingleShot(true);
    // note: inactive timer's remainingTime() returns -1
    if (timeout_msec >= 0) t.start(timeout_msec);
    qint64 bytesRead = 0;
    auto dataPtr = (char*)data;
    while (bytesRead < len){
        auto bytesNeeded = len - bytesRead;
        auto ret = d.read(dataPtr, bytesNeeded);
        if (ret == -1){
            throw std::runtime_error(d.errorString().toStdString());
        }
        qDebug()<<QString("Attempted to read %1 bytes, got %2 bytes")
                  .arg(bytesNeeded)
                  .arg(ret);
        if (ret == 0) {
            qDebug("Calling waitForReadyRead");
            bool ret = d.waitForReadyRead(t.remainingTime());
            if (!ret){
                qDebug()<<"waitForReadyRead failed";
                throw std::runtime_error(d.errorString().toStdString());
            }
        }
        else {
            bytesRead += ret;
            dataPtr += ret;
        }
        if (t.remainingTime() == 0)
            throw std::runtime_error("blockingRead timeout");
    }
}
QByteArray readWrapper(QIODevice &d, qint64 len, int timeout_msec = 10000)
{
    QByteArray ret;
    ret.resize(len);
    readWrapper(d, ret.data(), len, timeout_msec);
    return ret;
}
void writeWrapper(QIODevice &d, void *data, qint64 len)
{
    auto ret = d.write((char*)data, len);
    if (ret < len){
        throw std::runtime_error(QString("error writing data: %1")
                                 .arg(d.errorString())
                                 .toStdString());
    }
}
void writeWrapper(QIODevice &d, const QByteArray &data)
{
    writeWrapper(d, (void*)data.data(), data.size());
}
void handleConnection(QAbstractSocket* s)
{
    qDebug()<<__FUNCTION__;
    QByteArray data = "test s";
    qDebug()<<"writing data";
    writeWrapper(*s, data);
    qDebug()<<"reading data";
    auto readData = readWrapper(*s, 6);
    dumpvar(readData);
    s->disconnectFromHost();
    s->waitForDisconnected();
}
class SslServer: public QTcpServer
{
    // QTcpServer interface
protected:
    void incomingConnection(qintptr handle) override
    {
        QSslSocket s;
        if (!s.setSocketDescriptor(handle)){
            dumpvar(s.errorString());
            return;
        }
        s.setLocalCertificate("/home/piotrek/cert.pem");
        Q_ASSERT(!s.localCertificate().isNull());
        s.setPrivateKey("/home/piotrek/key.pem", QSsl::Rsa, QSsl::Pem, "test"); // last argument is private key decryption password
        Q_ASSERT(!s.privateKey().isNull());
        s.startServerEncryption();
        qDebug()<<"waiting for encrypted";
        if(!s.waitForEncrypted(10000)){
            dumpvar(s.errorString());
            dumpvar(s.sslErrors());
            return;
        }
        qDebug()<<"server encrypted";
        handleConnection(&s);
    }
};
void tcpserver()
{
    QTcpServer *s = new QTcpServer;
    QObject::connect(s, &QTcpServer::newConnection, [s]{
        handleConnection(s->nextPendingConnection());
    });
    s->listen(QHostAddress::Any, 1234);
}
void sslserver()
{
    auto s = new SslServer;
    s->listen(QHostAddress::Any, 1234);
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
//  tcpserver();
    sslserver();
    return a.exec();
}

用于生成自签名证书的命令:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

以下是 TCP 上的输出:

客户:

client connected
writing data
reading data
"Attempted to read 6 bytes, got 0 bytes"
Calling waitForReadyRead
"Attempted to read 6 bytes, got 6 bytes"
readData = "test s"
QAbstractSocket::waitForDisconnected() is not allowed in UnconnectedState

服务器:

handleConnection
writing data
reading data
"Attempted to read 6 bytes, got 0 bytes"
Calling waitForReadyRead
"Attempted to read 6 bytes, got 6 bytes"
readData = "test c"

使用SSL:

客户:

qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
client connected
writing data
reading data
"Attempted to read 6 bytes, got 6 bytes"
readData = "test s"

服务器:

qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
waiting for encrypted
server encrypted
handleConnection
writing data
reading data
"Attempted to read 6 bytes, got 0 bytes"
Calling waitForReadyRead
waitForReadyRead failed
terminate called after throwing an instance of 'std::runtime_error'
  what():  Network operation timed out

编辑:另外,如果我忽略waitForReadyRead((的返回值,read((仍然会一直返回0,即使我肯定在连接的另一端写入了6个字节。

因为您没有将套接字与信号和插槽异步使用,所以您必须选择哪一端先写入并在 waitForBytesWrite 中阻塞,然后在读取中阻塞,直到收到数据。而另一方以相反的顺序做同样的事情。

服务器:

void handleConnection(QAbstractSocket* s)
{
    qDebug()<<__FUNCTION__;
    QByteArray data = "test s";
    qDebug()<<"writing data";
    writeWrapper(*s, data);
    s->waitForBytesWritten(3000);
    qDebug()<<"reading data";
    auto readData = readWrapper(*s, 6);
    dumpvar(readData);
    s->disconnectFromHost();
    s->waitForDisconnected();
}

客户:

void handleConnection(QAbstractSocket& s)
{
    QByteArray data = "test c";
    qDebug()<<"reading data";
    auto readData = readWrapper(s, 6);
    dumpvar(readData);
    qDebug()<<"writing data";
    writeWrapper(s, data);
    s.waitForBytesWritten(3000);
    s.disconnectFromHost();
    s.waitForDisconnected();
}

服务器输出:

qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
waiting for encrypted
server encrypted
handleConnection
writing data
reading data
"Attempted to read 6 bytes, got 0 bytes"
Calling waitForReadyRead
"Attempted to read 6 bytes, got 6 bytes"
readData = "test c"
qt.network.ssl: QSslSocket::waitForDisconnected() is not allowed in UnconnectedState

客户端输出:

qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
client connected
reading data
"Attempted to read 6 bytes, got 6 bytes"
readData = "test s"
writing data
Press <RETURN> to close this window...