Название: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: IGHOR от Июль 04, 2013, 00:51
Есть у меня программа Qt Bitcoin Trader. Решил наконец то перейти с QHttp на QNetworkAccessManager. Но закрались подводные камни с параметром nonce. Все запросы должны доходить до сервера в той последовательности что были отправлены. В QHttp это работало как надо. Но не хватало возможности задать таймаут запроса и тп.
QNetworkAccessManager работает в параллельном режиме в 6 потоков. Можно ли задать ему только 1 поток?
В сервер надо посылать число с постоянным инкрементом. 1,2,3,4, если придет 1,2,4,3 то будет ошибка.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 04, 2013, 07:28
я такой код использую. если поможет и как-то оптимизируете код, выложите тут свой вариант. C++ (Qt) void Thread::GET(QNetworkAccessManager &qnam, int howReadAnswer, int headers, int redirect, QByteArray contentType, bool repeat) { QEventLoop loopGet; int badProxyCount=0; request.setRawHeader("Content-Type", contentType); REPEATGET: reply = qnam.get(request); reply->ignoreSslErrors(); //if (proxyType == "NoUseProxy") // QTimer timer; // timer.start(30000); // connect(&timer, SIGNAL(timeout()), &loopGet, SLOT(quit())); QTimer::singleShot(60000, &loopGet, SLOT(quit())); connect(reply, SIGNAL(finished()), &loopGet, SLOT(quit())); loopGet.exec(); //if (proxyType == "NoUseProxy") // if (timer.isActive()) // timer.stop(); //reply->deleteLater(); if (reply->error()) { qDebug() << "REPLY ERROR:" << reply->errorString() << reply->error(); if (repeat && badProxyCount<3) { badProxyCount++; qDebug() << "badProxyCountGet: " << badProxyCount; delete reply; goto REPEATGET; } } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ НАЧАЛО //////////////////////// replyHeaders.clear(); QList<QByteArray> listHeaders = reply->rawHeaderList(); foreach(QByteArray nameHeader, listHeaders) { QByteArray valueHeader = reply->rawHeader(nameHeader); replyHeaders.insert(nameHeader, valueHeader); } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ КОНЕЦ //////////////////////// if (headers == showHeaders) HTTPHEADERS(reply); location = QByteArray::fromPercentEncoding(reply->rawHeader("Location")); if (!reply->rawHeader("Ajax-Location").isEmpty()) { location = QByteArray::fromPercentEncoding(reply->rawHeader("Ajax-Location")); } if (redirect == autoRedirect) { if (!location.isEmpty()) { lastUrl = reply->url().toString(); redirectCount = 0; REDIRECT(qnam, howReadAnswer, showHeaders); return; } } // if (readAnswer) { // if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) // answer = uncompress(reply->readAll()); // } else { // answer = reply->readAll(); // } // } else { // answerBytes = reply->readAll(); // } answer.clear(); answerBytes.clear(); if (howReadAnswer == readAnswerToString) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answer = uncompress(reply->readAll()); } else { answer = reply->readAll(); } } else if (howReadAnswer == readAnswerToByteArray) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answerBytes = uncompress(reply->readAll()); } else { answerBytes = reply->readAll(); } } else if (howReadAnswer == readAnswerToOther) { if (reply->rawHeader("Content-Type").contains("image/gif")) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answerBytes = uncompress(reply->readAll()); } else { answerBytes = reply->readAll(); } } else { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answer = uncompress(reply->readAll()); } else { answer = reply->readAll(); } } } lastUrl = reply->url().toString(); delete reply; } void Thread::POST(QNetworkAccessManager &qnam, int howReadAnswer, int headers, int redirect, QByteArray contentType, bool repeat) { QEventLoop loopPost; int badProxyCount=0; REPEATPOST: request.setRawHeader("Content-Type", contentType); reply = qnam.post(request, postData); reply->ignoreSslErrors(); //if (proxyType == "NoUseProxy") // QTimer timer; // timer.start(30000); // connect(&timer, SIGNAL(timeout()), &loopPost, SLOT(quit())); QTimer::singleShot(60000, &loopPost, SLOT(quit())); connect(reply, SIGNAL(finished()), &loopPost, SLOT(quit())); loopPost.exec(); //if (proxyType == "NoUseProxy") // if (timer.isActive()) // timer.stop(); //reply->deleteLater(); if (reply->error()) { qDebug() << "REPLY ERROR:" << reply->errorString() << reply->error(); if (repeat && badProxyCount<3) { badProxyCount++; qDebug() << "badProxyCountPost: " << badProxyCount; delete reply; goto REPEATPOST; } } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ НАЧАЛО //////////////////////// replyHeaders.clear(); QList<QByteArray> listHeaders = reply->rawHeaderList(); foreach(QByteArray nameHeader, listHeaders) { QByteArray valueHeader = reply->rawHeader(nameHeader); replyHeaders.insert(nameHeader, valueHeader); } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ КОНЕЦ //////////////////////// if (headers == showHeaders) HTTPHEADERS(reply); location = QByteArray::fromPercentEncoding(reply->rawHeader("Location")); if (!reply->rawHeader("Ajax-Location").isEmpty()) { location = QByteArray::fromPercentEncoding(reply->rawHeader("Ajax-Location")); } if (redirect == autoRedirect) { if (!location.isEmpty()) { lastUrl = reply->url().toString(); redirectCount = 0; REDIRECT(qnam, howReadAnswer, showHeaders); return; } } // if (readAnswer) { // if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) // answer = uncompress(reply->readAll()); // } else { // answer = reply->readAll(); // } // } if (howReadAnswer == readAnswerToString) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answer = uncompress(reply->readAll()); } else { answer = reply->readAll(); } } else if (howReadAnswer == readAnswerToByteArray) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answerBytes = uncompress(reply->readAll()); } else { answerBytes = reply->readAll(); } } lastUrl = reply->url().toString(); delete reply; } void Thread::REDIRECT(QNetworkAccessManager &qnam, int howReadAnswer, int headers) { QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); // if (redirectUrl.isEmpty()) { // if (reply->rawHeader("Ajax-Location") == "registrationConfirm.html") { // redirectUrl = reply->url().resolved(QUrl(QString(reply->rawHeader("Ajax-Location")))); // //referer = redirectUrl.toEncoded(); // } else { // redirectUrl = QUrl(QString(reply->rawHeader("Ajax-Location"))); // request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); // request.setRawHeader("Wicket-Ajax", QByteArray()); // request.setRawHeader("Content-Type", QByteArray()); // } // } qDebug() << "redirect url do: " << redirectUrl; //request.setRawHeader("Referer", ""); // if (redirectUrl.toEncoded().startsWith("/")) { // redirectUrl = reply->url().resolved(redirectUrl); // qDebug() << "redirect url posle: " << redirectUrl; // } if (!redirectUrl.isEmpty() && redirectUrl.isRelative()) { //redirectUrl.toEncoded().startsWith("/") redirectUrl = reply->url().resolved(redirectUrl); qDebug() << "redirect url posle: " << redirectUrl; } else if (!reply->rawHeader("Ajax-Location").isEmpty()) { // if (redirectUrl.toString().endsWith(".html")) { request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); request.setRawHeader("Wicket-Ajax", QByteArray()); // } redirectUrl = reply->url().resolved(QUrl::fromPercentEncoding(reply->rawHeader("Ajax-Location"))); qDebug() << "redirect url Ajax-Location posle: " << redirectUrl; } else if (redirectUrl.isEmpty()) { qDebug() << endl << this->objectName() << endl << endl << "NO REDIRECT!!!" << endl << endl << endl; return; } //request.setRawHeader("Referer", lastUrl.toUtf8()); request.setRawHeader("Content-Type", QByteArray()); request.setUrl(redirectUrl); QEventLoop loopRedirect; int badProxyCount=0; delete reply; REPEATREDIRECT: reply = qnam.get(request); reply->ignoreSslErrors(); //if (proxyType == "NoUseProxy") // QTimer timer; // timer.start(30000); // connect(&timer, SIGNAL(timeout()), &loopRedirect, SLOT(quit())); QTimer::singleShot(60000, &loopRedirect, SLOT(quit())); connect(reply, SIGNAL(finished()), &loopRedirect, SLOT(quit())); loopRedirect.exec(); //if (proxyType == "NoUseProxy") // if (timer.isActive()) // timer.stop(); //reply->deleteLater(); qDebug() << reply->url(); if (reply->error()) { qDebug() << "REPLY ERROR:" << reply->errorString() << reply->error(); if (badProxyCount<3) { badProxyCount++; qDebug() << "badProxyCountRedirect: " << badProxyCount; delete reply; goto REPEATREDIRECT; } } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ НАЧАЛО //////////////////////// replyHeaders.clear(); QList<QByteArray> listHeaders = reply->rawHeaderList(); foreach(QByteArray nameHeader, listHeaders) { QByteArray valueHeader = reply->rawHeader(nameHeader); replyHeaders.insert(nameHeader, valueHeader); } ///////////////////////////////// ЗАПИСЬ ЗАГОЛОВКОВ КОНЕЦ //////////////////////// if (headers == showHeaders) HTTPHEADERS(reply); if ((!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl().isEmpty() || !reply->rawHeader("Ajax-Location").isEmpty()) && redirectCount < 33) { redirectCount++; REDIRECT(qnam, howReadAnswer, showHeaders); } else { // if (readAnswer) { // if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) // answer = uncompress(reply->readAll()); // } else { // answer = reply->readAll(); // } // } if (howReadAnswer == readAnswerToString) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answer = uncompress(reply->readAll()); } else { answer = reply->readAll(); } } else if (howReadAnswer == readAnswerToByteArray) { if (reply->rawHeader("Content-Encoding").contains("gzip")) { //if (gzip) answerBytes = uncompress(reply->readAll()); } else { answerBytes = reply->readAll(); } } locationRedirect = QByteArray::fromPercentEncoding(reply->rawHeader("Location")); if (!reply->rawHeader("Ajax-Location").isEmpty()) { locationRedirect = QByteArray::fromPercentEncoding(reply->rawHeader("Ajax-Location")); } lastUrl = reply->url().toString(); delete reply; } } void Thread::HTTPHEADERS(QNetworkReply *reply) { if (!SHOWHEADERS) return; qDebug() << endl << this->objectName() << reply->url(); qDebug() << "Request headers: "; QList<QByteArray> reqHeaders = reply->request().rawHeaderList(); //QList<QByteArray> reqHeaders = request.rawHeaderList(); foreach (QByteArray reqName, reqHeaders) { QByteArray reqValue = reply->request().rawHeader(reqName); //QByteArray reqValue = request.rawHeader(reqName); qDebug() << reqName << ": " << reqValue; } qDebug() << "Reply headers: "; QList<QByteArray> reqHeadersReply = reply->rawHeaderList(); foreach( QByteArray reqName, reqHeadersReply ) { QByteArray reqValue = reply->rawHeader( reqName ); qDebug() << reqName << ": " << reqValue; } } QByteArray Thread::uncompress(const QByteArray &data) { if (data.size() <= 4) { qWarning("uncompress: Input data is truncated"); return QByteArray(); } QByteArray result; int ret; z_stream strm; static const int CHUNK_SIZE = 1024; char out[CHUNK_SIZE]; /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = data.size(); strm.next_in = (Bytef*)(data.data()); ret = inflateInit2(&strm, 15 + 32); // gzip decoding if (ret != Z_OK) return QByteArray(); // run inflate() do { strm.avail_out = CHUNK_SIZE; strm.next_out = (Bytef*)(out); ret = inflate(&strm, Z_NO_FLUSH); Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered switch (ret) { case Z_NEED_DICT: ret = Z_DATA_ERROR; // and fall through case Z_DATA_ERROR: case Z_MEM_ERROR: (void)inflateEnd(&strm); return QByteArray(); } result.append(out, CHUNK_SIZE - strm.avail_out); } while (strm.avail_out == 0); // clean up and return inflateEnd(&strm); return result; }
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 04, 2013, 10:14
я такой код использую. если поможет и как-то оптимизируете код, выложите тут свой вариант.
Спасибо! Но хотелось бы обойтись без QEventLoop и 6 подключений висящих на портах. Пока что вижу что лучший вариант это вернуться на QHttp. Наверно стоит написать свой класс на базе QSslSocket. Как-то странно что QNetworkAccessManager нельзя настроить так. В Qt5 тоже нет возможности сделать один поток?
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 04, 2013, 19:15
а чем плох QEventLoop?
//6 подключений висящих на портах расскажите подробнее, что за 6 подключений? никогда не слышал о них
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 04, 2013, 19:50
6 подключений висящих на портах расскажите подробнее, что за 6 подключений? никогда не слышал о них
QHttp делает 1 подключение к серверу. А QNetworkAccessManager целых 6, при том это значение в коде прописано как const для десктопов. Note: QNetworkAccessManager queues the requests it receives. The number of requests executed in parallel is dependent on the protocol. Currently, for the HTTP protocol on desktop platforms, 6 requests are executed in parallel for one host/port combination. const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6; Откройте tcpview и подключитесь программой через QNetworkAccessManager увидите там больше одного подключения. Это означает что если послать 6 QNetworkRequest то они будут обрабатываться одновременно, и могут вернуться назад не в том порядке. http://www.prog.org.ru/topic_24065_0.html Можно сделать с помощью QEventLoop задержку, но оно все-равно биндит резервные подключения. Было бы прекрасно если в QNetworkAccessManager можно задать один паралельный запрос одновременно. К стати проект тут: https://sourceforge.net/projects/bitcointrader/
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 05, 2013, 07:32
QNetworkAccessManager больше для работы с веб-сайтами подходит, получается. для работы с сервером используйте сокеты.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 06, 2013, 17:10
QNetworkAccessManager больше для работы с веб-сайтами подходит, получается. для работы с сервером используйте сокеты.
Если использовать QSslSocket для Http запросов, то один ответ может быть разбит на несколько пакетов которые надо соединить. Вопрос как их правильно склеивать? Смотрите картинку.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 06, 2013, 20:48
без понятия. для моих целей QNetworkAccessManager хватает.
https://ru.wikipedia.org/wiki/HTTP https://tools.ietf.org/html/rfc2616 или гугл
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 06, 2013, 20:51
судя по http://forum.vingrad.ru/act-ST/f-466/t-344912.html 4 байта - в них содержится размер пакета. \r\n - перевод строки. используется в протоколе http. более подробно смотрите в документации :)
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 07, 2013, 12:12
судя по http://forum.vingrad.ru/act-ST/f-466/t-344912.html 4 байта - в них содержится размер пакета. \r\n - перевод строки. используется в протоколе http. более подробно смотрите в документации :)
Да, QNetworkAccessManager и QHttp это сами обрабатывают. Странно что в одних разделенных пакетах Http ответа есть этот размер а в других нет.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 08, 2013, 07:43
кстати, можете показать как вы делаете запрос? по идее чтобы "склеить" достаточно C++ (Qt) QByteArray & QByteArray::append ( const QByteArray & ba )
посмотрите http://qt-project.org/doc/qt-4.8/network-securesocketclient.html если Qt собиралась с примерами лежит примерно тут C:\Qt\4.8.4-mingw4.4\examples\network\securesocketclient\securesocketclient.pro возможно пригодятся заголовки при запросе к серверу по http-протоколу Accept-Encoding: gzip,deflate // если ответ придет в gzip, распаковать можно QByteArray uncompress(const QByteArray &data) // для экономии трафика Connection: keep-alive // и Connection: close // держать соединение открытым, если еще запросы планируются. закрыть - если последний запрос. типа меньше нагрузка на сервер.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 08, 2013, 14:38
кстати, можете показать как вы делаете запрос? по идее чтобы "склеить" достаточно C++ (Qt) QByteArray & QByteArray::append ( const QByteArray & ba )
Дело в поддержке сервера метода ответа chunked. Сдедал все сам с QSslSocket по документации http.
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: thechicho от Июль 09, 2013, 19:23
выложите код (куски кода), конкретно этого вопроса. желательно с пояснениями, может кому-то в будущем время сэкономит. и добавьте в заголовок темы "[РЕШЕНО]"
Название: Re: QNetworkAccessManager и параметр Nonce
Отправлено: IGHOR от Июль 09, 2013, 19:41
Решение: использовать QSslSocket и читать данные по спецификации HTTP http://ru.wikipedia.org/wiki/Chunked_transfer_encoding
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: thechicho от Июль 09, 2013, 20:05
//и читать данные по спецификации HTTP это не решение. покажите как вы делаете обработку Transfer-Encoding: chunked средствами Qt
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: IGHOR от Июль 13, 2013, 21:07
int currentLineDataType=0; while(socketSender->bytesAvailable()) { static QByteArray currentLine; currentLine=socketSender->readLine();
if(currentLine.isEmpty())continue; if(currentLine.startsWith("HTTP/1")) { if(!buffer.isEmpty())buffer.clear(); currentLineDataType=1; endOfPacket=false; continue; }
switch(currentLineDataType) { case 0: if(nextPacketMastBeSize&¤tLine.size()>1&¤tLine.size()<=8&¤tLine.right(2)=="\r\n") { currentLine.remove(currentLine.size()-2,2); quint16 currentChunkSize=currentLine.toUShort(0,16); if(currentChunkSize==0)endOfPacket=true; else packetChunkSize+=currentChunkSize; nextPacketMastBeSize=false; break; } else { if(currentLine.size()>1&¤tLine.right(2)=="\r\n") { currentLine.remove(currentLine.size()-2,2); nextPacketMastBeSize=true; } buffer.append(currentLine); } break; case 1: if(currentLine.endsWith("chunked")){packetIsChunked=true;break;} if(currentLine!="\r\n")break; currentLineDataType=0; nextPacketMastBeSize=true; break; } if(endOfPacket) { if(packetChunkSize<buffer.size())buffer.remove(0,buffer.size()-packetChunkSize); emit dataReceived(buffer);
sendPendingData(); } }
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: IGHOR от Июль 15, 2013, 14:47
Остался еще вопрос. Есть ли еще какой-то идентификатор пакета QSslSocket чтобы знать что тот что отправлен был получен. Например отправляю пакеты и по очереди получаю ответы: 1,2,3 А отправил 4 пакет и вышел таймаут, отсылаю следующий пакет номер 5, и тут внезапно приходит ответ от 4 пакета. Можно ли задать пакету id или есть другие варианты решения проблемы?
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: thechicho от Июль 15, 2013, 16:18
не отсылать пакет 5, пока не придет ответ от 4 пакета?
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: IGHOR от Июль 15, 2013, 16:46
не отсылать пакет 5, пока не придет ответ от 4 пакета?
Ну это так и работает. Но выходит таймаут, например 5 секунд. И шлю следующий пакет. А после этого приходит 4. Делать abort и опять connect это слишком долго.
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: thechicho от Июль 15, 2013, 17:13
имелось в виду увеличить таймаут, раз обязательно условие очередности прихода пакетов на сервер. либо, отсылать 5 пакет сразу за 4ым или через какой-то промежуток времени, не дожидаясь ответа от 4ого. так сделать, если есть уверенность, что 4ый пакет 100500 дойдет до сервера и 5ый пакет никак не сможет его обогнать (я хз как работают сокеты, но вроде как позже ушел, позже должен прийти) или еще как-нибудь :)
Название: Re: QNetworkAccessManager и параметр Nonce [РЕШЕНО]
Отправлено: IGHOR от Июль 15, 2013, 18:26
Там некоторые пакеты обязательны к получению, а некоторые не важные можно проигнорировать. И надо сделать так чтобы важный пакет сразу отправлялся, а не ждал 5 секунд на ответ не важного. В общем буду думать об оптимизации :)
|