LINUX.ORG.RU

Правильная обработка обрывов и ошибок QSslSocket

 ,


0

1

Гоняю данные по сети с помощью QSslSocket.
Непонятно как правильно обрабатывать обрывы и ошибки «правильно».

Чтобы упростить набросал пример в котором шлем запрос на вэбсервер и читаем пстрочно ответ:

#include "cnetworking.h"

CNetworking::CNetworking(QString host, quint16 port):
    b_sock_disconnected(false),
    b_sock_error_occured(false)
{

    ssl_sock = new QSslSocket();

    connect(ssl_sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(SockErrorSlot()));
    connect(ssl_sock, SIGNAL(disconnected()), this, SLOT(SockDisconnectedSlot()));
    ssl_sock->connectToHostEncrypted(host, port);

    //Сразу ждем зашифрованного соединения
    if(!ssl_sock->waitForEncrypted() ) {

        SetErrors();
        return;
    }

    //Шлем какие-то данные
    ssl_sock->write("GET / HTTP/1.1\r\nHost: yandex.ru\r\n\r\n");

    //Читаем построчно до признака окончания
    QByteArray ba;
    bool flag = true;
    do {

        //Если что-то с соединеием не так...
        if(!isConnectionOk()) {

            SetErrors();
            return;
        }

        if(ssl_sock->waitForReadyRead()) {

            if(ssl_sock->canReadLine()) {
                ba = ssl_sock->readLine();//В реальности тут что-то делаем с данными
                if(ba == "\r\n")
                    flag = false;
            }
        }

    }while(flag);

}

//Все ли нормально с соединением
bool CNetworking::isConnectionOk() {

    if(b_sock_disconnected == true || b_sock_error_occured == true)
        return false;

    return true;
}

void CNetworking::SetErrors() {

    sock_error = ssl_sock->error();
    sock_error_str = ssl_sock->errorString();
}

QString CNetworking::GetErrorStr() {

    return sock_error_str;
}

int CNetworking::GetError() {

    return sock_error;
}

void CNetworking::SockDisconnectedSlot() {

    qDebug() << "SockDisconnectedSlot";
    b_sock_disconnected = true;
}

void CNetworking::SockErrorSlot(QAbstractSocket::SocketError) {

    b_sock_error_occured = true;
    qDebug() << "SockErrorSlot";
    SetErrors();
}

CNetworking::~CNetworking() {

}

-----------------------------------

#ifndef CNetworking_H
#define CNetworking_H

#include <QSslSocket>
#include <QString>

class CNetworking: public QObject
{
        Q_OBJECT
public:
    CNetworking(QString host, quint16 port/*, quint16 data_read_timeout_msec*/);
    int GetError();
    QString GetErrorStr();
    virtual ~CNetworking();

private:

    bool isConnectionOk();
    
    void SetErrors();

    QSslSocket* ssl_sock;

    bool b_sock_disconnected;
    bool b_sock_error_occured;
    
    QString sock_error_str;
    QAbstractSocket::SocketError sock_error;

private slots:
    void SockDisconnectedSlot();
    void SockErrorSlot(QAbstractSocket::SocketError);
};

#endif // CNetworking_H


У меня есть сомнения:
Кажется странным везде перед чтением/записью лепить проверку

        //Если что-то с соединеием не так...
        if(!isConnectionOk()) {

            SetErrors();
            return;
        }

интуиция говорит что должен быть какой-то способ на уровне сигналов/слотов (завершить выполнение метода???)

И другой момент касается использования waitForReadyRead(). В документации сказано
Reimplemented from QAbstractSocket::waitForReadyRead().
Смотрим описание QAbstractSocket::waitForReadyRead():
Note: This function may fail randomly on Windows. Consider using the event loop and the readyRead() signal if your software will run on Windows.
Мой код должен запускаться и под линукс и под виндой. Стоит ли обращать внимание на этот Note и для винды делать что-то типа:
QEventLoop myloop;

myloop.connect(ssl_sock, SIGNAL(connected()), SLOT(quit()));
myloop.connect(ssl_sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(quit()));

myloop.exec();

Благодарю за любую помощь, критику или подсказки


Ты пытаешься написать синхронный код используя асинхронное API. Работай с сокетом асинхронно, прицепившись к его сигналу readyRead, читай данные в слоте. Для обработки ошибок прицепись слотом к сигналу error(QAbstractSocket::SocketError). Дополнительный event loop создавать не надо, он уже есть в твоей программе, если ты создавал в main() объект QApplication или QCoreApplication.

WRG ★★★★
()
Ответ на: комментарий от WRG

не хотел связываться с асинхронностью. много этапов последовательных чтений-записей которые придется помещать в разные слоты и переключать их.
А может быть это и удобно в некоторых случаях...

Как быть с обрывами и ошибками если они возникают? так же везде перед чтением-записью проверять не вызывались ли сигналы ошибок?
Чем плох линейных синхронный код?

ocr
() автор топика
Ответ на: комментарий от WRG

и по поводу QSslSocket::waitForReadyRead непонятно безопасно для винды или нет.
В справке написано что метод

Reimplemented from [b]QAbstractSocket::waitForReadyRead().[/b]

А в описании QAbstractSocket::waitForReadyRead() сказано
Note: This function may fail randomly on Windows. Consider using the event loop and the readyRead() signal if your software will run on Windows.

Если один метод Reimplemented другого, то его касается проблема с виндой?

ocr
() автор топика
Ответ на: комментарий от WRG

Слушай этого человека или обрабатывай коды ошибок.

С одной оговоркой, EventLoop таки можно создать, но тогда и только тогда, когда у тебя возникнут требования к времени отклика или ты перестанешь успевать разгребать буфер сокета. Разумеется в отдельном потоке.

P.S.: именование классов с префикса C так отдаёт MFC и прочими ATL, что лично я бы увидев такое на житхабе дважды подумал бы стоит ли звать на собеседование даже не вникая в код. Травма детства понимаешь ли.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Видел я этот MFC и сам был травмирован, хотя ни строчки на нем не написал.
Именование классов с «С» из книг)

Мне нравится то что предлагает WRG, но никак не могу найти выставление таймаутов при работе с асинхронной моделью, похоже надо делать таймеры руками.
Кроме того, при поытке использования уже существующего цикла обработки событий

qApp->exec()
говорит что QCoreApplication::exec: The event loop is already running

А с синхронной моделью непонятно безпасно ли QSslSocket::waitForReadyRead под Windows

Покачто тупик(

ocr
() автор топика
Ответ на: комментарий от ocr

похоже надо делать таймеры руками.

Да, таймауты придётся делать руками.

говорит что QCoreApplication::exec: The event loop is already running

Event loop был запущен в main()

int main()
{
    QCoreApplication app(argc, argv);
    ...
    return app.exec(); // <-- вот запуск event loop
}
Второй раз его запускать не нужно.

безпасно ли QSslSocket::waitForReadyRead под Windows

В доках явно сказано, что не безопасно.

По сути, тебе нужен класс CNetworking с машиной состояний внутри, читающий в слотах данные из сокета, обрабатывающий ошибки и отслеживающий таймауты.

WRG ★★★★
()
Ответ на: комментарий от ocr

А с синхронной моделью непонятно безпасно ли QSslSocket::waitForReadyRead под Windows

Это не синхронная модель, а низкоуровневая часть асинхронной. Суть инкапсулированный select на данном дескрипторе.

Под виндой select и poll работают на сетевых сокетах. Могут не работать в случае если ты скормишь туда дескриптор другого устройства ввода вывода, которые в остальном совместимы с сокетом.

pon4ik ★★★★★
()
Ответ на: комментарий от WRG

немного иная ситуация:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}
Получается что мне нужно передать ссылку на объект QApplication в объект MainWindow, а потом уже там передавать в объект своего класса. Я не нашел другого способа получить доступ к нему
Другой вариант попробовать сделать плохим способом - сделать объект QApplication глобальным.

Если я буду использовать свой класс в том виде в котором его обсуждаем в нескольких потоках запущенных QtConcurrent, это будет потокобезопасно? Там ведь OpenSSL используется

ocr
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.