Pull to refresh

Comments 38

Полностью согласен с вами на счёт libcurl!
На Qt, если совсем по простому, то лучше использовать QNetworkAccessManager.
А не могли бы привести пример кода? Мне в реальном проекте показалось что с QNetworkAccessManager все получается сложнее и развесистее.
Странно, как по мне как раз наоборот. Пример из документации для GET:

QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); manager->get(QNetworkRequest(QUrl("http://qt-project.org")));

Или, тут когда-то давно писал про POST.
Сформировать запрос несложно и через QTcpSocket, а распарсить ответ?
А в чем проблема? QNetworkReply является QIODevice, т.е. нет никакой проблемы скормить его тому же QJson. Конечно, пример выше сильно упрощен и на реальном проекте скорей всего понадобятся подписки на сигналы QNetworkReply для того чтобы, например, читать данные как только они готовы, следить за процессом и т.п.
Не совсем понятно какие тотальные преимущества по сравнению с низкоуровневым QTcpSocket? Тот тоже QIODevice.
Как я понимаю, встроенная асинхронность:
QNetworkAccessManager has an asynchronous API. When the replyFinished slot above is called, the parameter it takes is the QNetworkReply object containing the downloaded data as well as meta-data (headers, etc.).
QTcpSocket тоже асинхронен «из коробки».
Правда QNetworkReply хотя бы парсит ответ на заголовок и тело, что уже плюс.
QTcpSocket тоже асинхронен «из коробки».

Что-то в документации по этому поводу ни слова.
Сори нашел.
QNetworkAccessManager — высокоуровневая обертка над сокетами. Я думаю, что в большинстве случаев, будет достаточно этого класса, в котором многие вещи уже сделаны за нас, например, уведомление о прогрессе. Я понимаю, что можно сделать самому, но гораздо проще связать прогресс бар с этим сигналом и забыть. То же касается многих вещей по мелочи, вроде авторизации, куков и т.п.

Пример из статьи переписанный с QNetworkAccessManager, так как считаю, что пример из статьи стоит убрать из-за неактуальности. Было бы отлично, если бы Вы переписали этот пример с сокетами, для полноты картины :-).

Handler::Handler(QObject *parent) : QObject(parent)
{
    manager = new QNetworkAccessManager(this);
}
void Handler::doHttp()
{
    QNetworkRequest request;
    request.setUrl(QUrl("google.com"));
    QNetworkReply *reply = manager->get(request);
    connect(reply, SIGNAL(finished())this, SLOT(requestFinished()));
    connect(reply, SIGNAL(error(QNetworkReply::NetworkError))this, SLOT(requestError(QNetworkReply::NetworkError)));
}
void Handler::requestFinished()
{
    QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
    QByteArray bytes = reply->readAll();
    QString stringData(bytes);
    qDebug() << stringData;
    reply->deleteLater();
}
void Handler::requestError(QNetworkReply::NetworkError error)
{
}
Было бы отлично, если бы Вы переписали этот пример с сокетами

Пожалуйста. Нашел небольшой примерчик в каком-то старом проекте.

Обертка над сокетом:
RemoteRequest::RemoteRequest(const QString &request, QObject *receiver, const char * slot):
    QObject(0)
    m_request(request)
{
    m_socket = new QTcpSocket(this);
    connect(m_socket, SIGNAL(connected()), this, SLOT(connected()));
    connect(m_socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    if(receiver)
        connect(this, SIGNAL(dataReceived(QByteArray)), receiver, slot);
    m_socket->connectToHost(Settings::server(), Settings::port());
}

void RemoteRequest::connected()
{
    m_socket->write("GET " + m_request.toUtf8());
}

void RemoteRequest::readyRead()
{
    QByteArray result = m_socket->readAll();
    emit dataReceived(result);
}

и вызов
  new RemoteRequest("/index.html", this, SLOT(dataReceived(QByteArray)));
В QNetworkAccessManager есть ограничения на настройку количества подключений.
Используются дефолтные (для HTTP) 4 одновременные подключения к серверу, и если нужна строгая последовательность ответов то ничего настроить невозможно.
А так же невозможно забиндить конкретный сетевой адаптер для использования нескольких «интернетов» параллельно.
Неплохо было бы пометить, какие из них C, а какие именно C++, для наглядности.
Qt
В довольно старой уже версии 4.х QHttp уже назывался deprecated и не рекомендовался к использованию. В современной 5.х его выпилили совсем.
Может быть стоит пример в статье пометить как Qt 4.x и добавить пример с QNetworkAccessManager с пометкой Qt 5.x?
Однозначно. И Qt 4.x под спойлер и указание как устарвший, чтобы не смущать новичков
QNetworkAccessManager появился в Qt 4.4, и подозреваю, ваш код «для Qt5» будет прекрасно работать и в Qt4 (хотя, возможно, что-то по мелочи понадобится изменить). А QHttp стал deprecated аж в Qt 4.6, т.е. 4 с лишним года назад, и сейчас упоминать эту древность нет смысла. Рекомендую этот пример удалить вообще.

Также есть несколько замечаний по примеру для Qt5:

> if(reply->error())

Правильно:
if(reply->error() != QNetworkReply::NoError)


QNetworkReply::error возвращает значение enum, а не boolean, и полагаться на численные значения элементов enum — дурной тон.

> qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString();
>…

QNetworkReply::header возвращает QVariant, который можно напрямую передать в qDebug() без дополнительных преобразований. Если вы хотели показать, как преобразовать QVariant в другой тип, лучше сделать присвоение к переменной этого типа. В текущем виде это не имеет особого смысла.

> QFile *file = new QFile(«C:/Qt/Dummy/downloaded.txt»);

Удобнее и безопаснее создавать файл в виде
QFile file("C:/Qt/Dummy/downloaded.txt"); 

При этом отпадает необходимость вызова delete. См. официальный пример.

> file->flush();
> file->close();

flush не нужен, так как он делается автоматически при вызове close. А close не нужен, так как он делается автоматически при удалении объекта.

Если эти недостатки устранить, пример станет короче и понятней.
reply->error() и и QFile — исправил. А toString() оставил, поскольку основная задача при разборе ответа — не передать заголовок в виде QVariant кому-то, кто умеет с ним что-то сделать, а всё-таки получить заголовок в виде строки и дальше заниматься её анализом.
Стоит сделать акцент в теме на то, что это библиотеки http-клиентов. Ну и да, реквестирую подобный пост про библиотеки, реализующие сервера.
В принципе, многие библиотеки из статьи дают возможность написать и сервер (POCO, boost, Casablanca, возможно и другие). Я когда-то писал о веб-сервере на POCO, а с остальным в качестве сервера не работал.
Ага, когда самим потребовалось, было сделано на boost.asio. Т.е. что написать можно — это понятно, но, как минимум упираемся в парсер, обработку разных ответов, авторизацию и т.п., что достаточно рутинно.
Рекомендую mongoose. Кросплатформенная, на C, легковесная (пара файлов, 5к строк). Правда GPL.
Еще бы табличку со сравнением, например, асинхронности, потокобезопасности, поддержки SSL, зависимостей, расширяемости, лицензий.
Спасибо за обзор! Обязательно вернусь к нему, когда придёт время выбора библиотечки.
Можно в Вашу коллекцию еще и libevent добавить. В нем есть неплохая поддержка HTTP как для разработки серверной части, так и для клиента.
Спасибо, однозначно в избранное. Страшно подумать, сколько бы у меня заняло времени собственное исследование вроде этого. А здесь просмотрел примеры за две минуты и уже составил впечатление.
А в какой конфигурации libcurl? По умолчанию в ней много протоколов, лишние можно отключить через макроопределения.
Более того, WinInet не рекоммендуется (написано, в частности, по ссылке выше) для использования в сервереных средах ( и есть реальный опыт проблем с ним)
Ага, а для не-серверных как-раз рекомендуют WinInet. В общем, я добавил WinHttp в статью, пусть будет, не помешает.
А где рекомендуют WinInet?
Мне казалось, что WinHttp сейчас для всех случаев его заменяет.
Ну вот: «With a few exceptions, WinINet is a superset of WinHTTP. When selecting between the two, you should use WinINet, unless you plan to run within a service or service-like process that requires impersonation and session isolation.»
Boost.asio сам по себе http не поддерживает, зато с его применением написан pion. Использовал эту библиотеку в проекте, где требовалось реализовать свой протокол поверх http(s), она оказалось очень удобной.

Хотя если требуется только пару страничек загрузить, libcurl будет гораздо проще.
отличная шпаргалка. Сам долгое время мучился с портированием libcurl под MarmaladeSDK, так и не, смог привести порт в стабильное состояние. Специфика SDK требовала строгой однотредности и асинхронности, терпимости к реализации socket api. В этом смысле из списка привлекателен HappyHttp, но его декларация асинхронности не соответствует действительности: gethostbyname не асинхронен.
Sign up to leave a comment.