libCurl тайм-аут загрузки данных не работает

0

У меня есть кросс-платформенное встроенное клиентское приложение libCurl, работающее на powerpc, которое ведет себя по-разному с его коллегой окон. Основная проблема заключается в том, что удаленный сервер, который мой клиент загружает файл, выполняет очень длительную операцию до возвращения ответа 226 (что указывает на успешную загрузку). Удаленный FTP-сервер на самом деле выполняет флеш-реламацию в это время, и эта операция может занять до 900 секунд. По сути, я пытаюсь использовать тайм-аут неактивности данных в ожидании удаленного 226 или ответа на ошибку.

В Windows это отлично работает, однако на встроенном клиенте PowerPC (где мы связываемся с последней библиотекой libCurl-7.39.0, скомпилированной с использованием инструментальной схемы Mentor Graphics Code Sourcery для PowerGNU), клиент истекает через ровно 60 секунд бездействия FTP.

То, как я устанавливаю таймеры, показано в приведенном ниже коде (обратите внимание, что я гарантирую, что значение CURLOPT_FTP_RESPONSE_TIMEOUT на 1 секунду меньше, чем CURLOPT_TIMEOUT. Кроме того, стоит отметить, что CURLOPT_CONNECTTIMEOUT установлен на 60 секунд (возможно, это совпадение, но требуется CURLOPT_CONNECTTIMEOUT (т.е. 60) секунд для бездействия таймаута на клиенте powerPC linux). Интересно, есть ли какая-то ошибка, скрытая в CURLOPT_CONNECTTIMEOUT, переписывает или искажает CURLOPT_FTP_RESPONSE_TIMEOUT на клиенте linux?

Кроме того, мои функции завивки, похоже, работают нормально. Я прочитал статью о внедрении таймеров в libCurl, где кажется, что таймеры организованы в каком-то порядке "от первого до истечения", возможно, в то время, когда я обновляю значение CURLOPT_FTP_RESPONSE_TIMEOUT по умолчанию (которое по умолчанию неопределенно), его причины вставки повреждение очереди таймера.

// if updating the module could potentially
// cause flash reclamation, set the command to response FTP
// timer to include both delivery time + the max expected
// time for the file put for the biggest file over BASE2 or BASET
auto flashReclTimeout = rContext.getFlashReclTimeout();
if (flashReclTimeout) {
    auto timeoutSecs = duration_cast<seconds>(flashReclTimeout.get());
    auto res = curl_easy_setopt(rContext.getCurlHandle(),
        CURLOPT_TIMEOUT, timeoutSecs.count()+1);
    res = curl_easy_setopt(rContext.getCurlHandle(),
        CURLOPT_FTP_RESPONSE_TIMEOUT, timeoutSecs.count());
    ss  << ", [flash reclamation timeout "
        << timeoutSecs.count()
        << "(s)]";
}
LOG_EVT_INFO(gEvtLog) << rLogPrefix << ss.str() << std::endl;

Мои параметры libCurl по умолчанию настраиваются следующим образом

/**
 * Sets the curl options using the current mContextInfo.
 *
 * This never sets the URI curl field as this must be
 * done outside the context object.
 */
void
SLDBContext::setCurlOptions() {
    CURL* pCurl = mCurlHandle.get();
    // reset all curl context info
    curl_easy_reset(pCurl);
    // DEOS does not support EPSV or EPRT

    auto res = curl_easy_setopt(pCurl, CURLOPT_FTP_USE_EPSV, 0L);
    res = curl_easy_setopt(pCurl, CURLOPT_FTP_USE_EPRT, 0L);
    res = curl_easy_setopt(pCurl, CURLOPT_NOSIGNAL, 1L);
#if 0
    // send out TCP keep-alive probes - not required
    res = curl_easy_setopt(pCurl, CURLOPT_TCP_KEEPALIVE, 1L);
    // check to ensure that this is supported
    if (res == CURLE_OK) {
        // wait for at least 30 seconds before sending keep-alive probes
        // every 2 seconds
        res = curl_easy_setopt(pCurl, CURLOPT_TCP_KEEPIDLE, 30L);
        res = curl_easy_setopt(pCurl, CURLOPT_TCP_KEEPINTVL, 30L);
    }
#endif
    // do not perform CWD when traversing the pseudo directories
    res = curl_easy_setopt(pCurl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
    res = curl_easy_setopt(pCurl, CURLOPT_CONNECTTIMEOUT, getConnectTimeoutSecs());

    if (!isPASVMode()) {
        res = curl_easy_setopt(pCurl, CURLOPT_FTPPORT, "-");
    }
    // used to capture header traffic
    if (mHeaderCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_WRITEHEADER, mpHeaderStream);
        res = curl_easy_setopt(pCurl, CURLOPT_HEADERFUNCTION, mHeaderCallback);
    }
    // for FTP GET operations
    if (mWriteDataCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, &mScratchBuffer);
        res = curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, mWriteDataCallback);
    }
    // for FTP PUT operations
    if (mReadFileCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_READFUNCTION, mReadFileCallback);
    }

    // @JC this feature may be causing slowdowns on the target platform
#if 0
    // capture error details to this buffer
    res = curl_easy_setopt(pCurl, CURLOPT_ERRORBUFFER, mErrorBuffer.get());
#endif

    // progress callback used to track upload progress only
    if (mProgressCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_XFERINFOFUNCTION, mProgressCallback);
        res = curl_easy_setopt(pCurl, CURLOPT_NOPROGRESS, 0L);
        res = curl_easy_setopt(pCurl, CURLOPT_XFERINFODATA, nullptr);
    }

    // verbose logging
    if (mDebuggingCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_VERBOSE, 1L);
        res = curl_easy_setopt(pCurl, CURLOPT_DEBUGFUNCTION, mDebuggingCallback);
        res = curl_easy_setopt(pCurl, CURLOPT_DEBUGDATA, nullptr);
    } else {
        res = curl_easy_setopt(pCurl, CURLOPT_VERBOSE, 0L);
        res = curl_easy_setopt(pCurl, CURLOPT_DEBUGDATA, nullptr);
    }

    // disable Nagle algorithm - to fix slowdown in bulk transfers
    // with large data files @JC not necessary
    // res = curl_easy_setopt(pCurl, CURLOPT_TCP_NODELAY, 1L);
    if (mSocketOptionCallback) {
        res = curl_easy_setopt(pCurl, CURLOPT_SOCKOPTDATA, nullptr);
        res = curl_easy_setopt(pCurl, CURLOPT_SOCKOPTFUNCTION, mSocketOptionCallback);
    }
}
  • 0
    возможно, вам также нужен curl.haxx.se/libcurl/c/CURLOPT_ACCEPTTIMEOUT_MS.html, если при передаче данных используется пассивный ftp?
  • 0
    Если вы хотите «тайм-аут простоя», вы должны настроить PROGRESSFUNCTION и реализовать свою собственную логику, чтобы определить, если соединение застряло.
Теги:
ftp
libcurl
powerpc

1 ответ

0
Лучший ответ

На самом деле я нашел проблему - оказался в основном моей проблемой:

после значительной отладки и разбрызгивания распечаток на нашей целевой платформе выясняется, что источником ошибки было 75% проблемы с приложением (мой) и 25% (на мой взгляд) проблемы с libCurl из-за слабости использования слабо связанных va_args для извлечения аргументов из списка аргументов переменной длины при настройке параметров libCurl. Проблема заключалась в неявном длинном длинном преобразовании, а также связанной с Endian проблеме на платформе PowerPC, которая не была проблемой на платформе Windows.

Я использую libCurl для наших потребностей FTP-клиента в приложении C++, связанном со стандартной библиотекой C++. Я использую std :: chrono :: seconds для установки времени и продолжительности опций libCurl. Под обложками, однако, std :: chrono :: seconds - довольно сложный тип шаблона с внутренним представлением n 8 байтов PPC "long long", который отличается от 4-байтового PPC "long", который жестко закодирован в вариантах ниже, Из-за ослабленной связи между переданным в длинном длинном аргументе и фактическим "длинным" значение, установленное в CURLOPT_SERVER_RESPONSE_TIMEOUT, фактически было неправильным 4 байтами из 8-байтового "длинного длинного" на платформе Power PC. Я подтвердил это, написав фрагмент кода, чтобы проверить, как он работает в Windows, а не на нашей 32-битной PPC-встроенной цели.

То, как я установил исправление кода на уровне приложения, было, гарантируя, что было явное приведение к тому же типу, что и второй параметр va_arg - это требовалось, поскольку метод seconds :: count() возвращает длинный длинный, без этого Параметр CURLOPT_SERVER_RESPONSE_TIMEOUT был на удивление установлен на 0. Надеюсь, это полезно

if (flashReclTimeout) {
    // fix for broken flash reclamation timer on target platform
    // caused by 'long long' to 'long' conversion always
    // setting a 0 in the associated timers.
    auto timeoutSecs = duration_cast<seconds>(flashReclTimeout.get());
    /*auto res = */curl_easy_setopt(rContext.getCurlHandle(),
        CURLOPT_TIMEOUT, static_cast<long>(timeoutSecs.count() + 1));
    /*auto res = */curl_easy_setopt(rContext.getCurlHandle(),
        CURLOPT_FTP_RESPONSE_TIMEOUT, static_cast<long>(timeoutSecs.count()));
    ss  << ", [flash reclamation timeout "
        << timeoutSecs.count()
        << "(s)]";
}

Вот реализация в libCurl, где установлен CURLOPT_SERVER_RESPONSE_TIMEOUT (является синонимом опции CURLOPT_FTP_RESPONSE_TIMEOUT, которую я использовал в своем приложении.

CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
                     va_list param)
{
  char *argptr;
  CURLcode result = CURLE_OK;
  long arg;
#ifndef CURL_DISABLE_HTTP
  curl_off_t bigsize;
#endif

  switch(option) {
  case CURLOPT_DNS_CACHE_TIMEOUT:
 .  . .

  case CURLOPT_SERVER_RESPONSE_TIMEOUT:
    /*
     * Option that specifies how quickly an server response must be obtained
     * before it is considered failure. For pingpong protocols.
     */
    data->set.server_response_timeout = va_arg( param , long ) * 1000;
    break;

Дэн Фандрич на форуме пользователя libCurl правильно указал:

CURLOPT_FTP_RESPONSE_TIMEOUT (ранее CURLOPT_SERVER_RESPONSE_TIMEOUT) документирован, чтобы занять много времени. В этом нет никакой двусмысленности. Поскольку curl_easy_setopt использует varargs, в этой ситуации нет иного выбора, кроме кастинга, или с любым другим аргументом curl_easy_setopt, который не соответствует запрашиваемому типу. Я рад, что вы нашли источник проблем в своей программе, но поскольку справочная страница curl_easy_setopt говорит:

Внимательно прочитайте это руководство, так как плохие входные значения могут привести к тому, что libcurl будет плохо себя вести!

И Дэн Стейнберг, главный консультант/автор большинства LibCurl, ответил на мое утверждение, что varargs является слабым api, который подвержен ошибкам пользователя:

Да, использование varargs для этого было, вероятно, не самым мудрым выбором дизайна, когда мы создали API около 14 лет назад, но также почему мы постоянно подчеркиваем точный тип переменной, который нужно передать для каждой опции.

Макромания typecheck-gcc.h - это еще один способ помочь пользователям обнаружить эти ошибки.

Итак, чтобы подвести итог, фактическая проблема была моей - не правильно прочитав документацию, однако основная слабость varargs api способствовала присущей ему слабости в API - извлеченный урок был прочитан в руководстве и очень осторожно относился к любым автоматическим преобразованиям типов базовых типов из типов std :: chrono :: duration в моем конкретном случае.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню