У меня есть кросс-платформенное встроенное клиентское приложение libCurl, работающее на PowerPC, которое ведет себя не так, как его аналог Windows. Основная проблема заключается в том, что удаленный сервер, на который мой клиент загружает файл, выполняет очень долгую операцию перед возвратом ответа 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_RESEOUTT на 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);
}
}
На самом деле я нашел проблему — оказалось, в основном, моя проблема:
после значительной отладки и разбрасывания распечаток на нашей целевой платформе выясняется, что источником ошибки было 75% проблемы приложения (моей) и 25% (на мой взгляд) проблемы libCurl из-за слабости использования слабосвязанных va_args извлечь аргументы из списка аргументов переменной длины при настройке параметров libCurl. Проблема была связана с неявным преобразованием «long long» в «long», а также с проблемой Endian на платформе PowerPC, которая не была проблемой на платформе Windows.
Я использую libCurl для нужд нашего FTP-клиента в приложении C ++, связанном со стандартной библиотекой шаблонов C ++. Я использую объекты std :: chrono :: секунд, чтобы установить параметры времени и продолжительности libCurl. Однако под прикрытием std :: chrono :: seconds — это довольно сложный тип шаблона с внутренним представлением n-байтового PPC длиной 8 байтов, который отличается от 4-байтового байта PPC длиной, который жестко задан в приведенных ниже параметрах. , Из-за слабой связи между передаваемым в аргументе long long и фактическим long значением, установленным в CURLOPT_SERVER_RESPONSE_TIMEOUT, были фактически неправильные 4 байта из 8 long байта long long на платформе мощного ПК. Я подтвердил это, написав фрагмент кода, чтобы проверить, как он работает на Windows, а не на нашей 32-битной встроенной цели PPC.
Я установил фиксированный код на уровне приложения, обеспечив явное приведение к тому же типу, что и 2-й параметр va_arg — это было необходимо, поскольку метод seconds :: count () возвращает long long, без этого Опция 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 — это еще один способ, которым мы пытаемся помочь пользователям
обнаружить эти ошибки.
Подводя итог, можно сказать, что настоящая проблема была моей — неправильное чтение документации, однако слабость API varargs привела к внутреннему недостатку API — извлеченный урок был прочитан в руководстве и очень внимательно относился к любым автоматическим преобразованиям типов. базовых типов из типов std :: chrono :: duration в моем конкретном случае.