libCurl 上传数据不活动超时不起作用



我有一个跨平台嵌入式libCurl客户端应用程序在Powerpc上运行,其行为与Windows对应物不同。 基本问题是我的客户端上传文件的远程服务器在返回 226 响应(指示上传成功(之前执行了很长的操作。 此时远程 FTP 服务器实际上正在执行闪存回收,此操作最多可能需要 900 秒。 实际上,我正在尝试在等待远程 226 或错误响应时使用数据不活动超时。

Windows上,这工作正常,但是在PowerPC嵌入式客户端(我们链接到使用PowerGNU的Mentor Graphics Code Sourcery工具链编译的最新libCurl-7.39.0库(上,客户端在FTP不活动60秒后超时。

设置计时器的方式如下面截取的代码所示(请注意,我确保CURLOPT_FTP_RESPONSE_TIMEOUT的值比CURLOPT_TIMEOUT低 1 秒。 此外,值得注意的是,CURLOPT_CONNECTTIMEOUT设置为 60 秒(也许这是巧合,但在 powerPC linux 客户端上不活动超时需要 CURLOPT_CONNECTTIMEOUT(即 60(秒(。 我想知道是否潜伏在覆盖或损坏 linux 客户端CURLOPT_FTP_RESPONSE_TIMEOUT CURLOPT_CONNECTTIMEOUT中潜伏着一些错误?

除此之外,我的卷曲选项似乎工作正常。 我读了一篇关于在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 问题,这是由于在设置 libCurl 选项时使用松散耦合va_args从可变长度参数列表中提取参数的弱点。 该问题与隐含的"长长"到"长"转换有关,并且与PowerPC平台上的字节序相关问题有关,这在Windows平台上不是问题。

我在C++应用程序中使用 libCurl 来满足我们的 FTP 客户端需求 - 与标准模板C++库链接。 我使用 std::chrono::seconds 对象来设置时间和持续时间 libCurl 选项。 然而,在幕后,std::chrono::seconds是一个相当复杂的模板类型,其内部表示为n 8字节PPC"长长",这与以下选项中硬编码的4字节PPC"长"不同。 由于传入的"long long"参数与实际的"long"之间的松散耦合,CURLOPT_SERVER_RESPONSE_TIMEOUT中设置的值实际上是Power PC平台上8字节"long long"中不正确的4个字节。 我通过编写一段代码来验证它在 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;

Dan Fandrich在libCurl用户论坛上正确地指出:

CURLOPT_FTP_RESPONSE_TIMEOUT(以前称为 CURLOPT_SERVER_RESPONSE_TIMEOUT(被记录为需要很长时间。有 对此毫不含糊。由于curl_easy_setopt使用varargs,因此有 在这种情况下,除了选角之外别无选择,或者任何 curl_easy_setopt与请求不匹配的其他参数 类型。 我很高兴您找到了程序中问题的根源,但是 正如curl_easy_setopt的手册页所说:

请仔细阅读本手册,因为错误的输入值可能会导致 libcurl 表现不好!

Dan Steinberg,大多数 LibCurl 的维护者/作者回应了我的断言,即 varargs 是一个容易出现用户错误的弱 api:

是的,为此使用 varargs 可能不是最明智的设计选择 当我们在大约 14 年前创建 API 时,这也是为什么我们 持续强调要为每个选项传入的确切变量类型。

typecheck-gcc.h macromania 是我们尝试帮助用户 发现这些错误。

总而言之,实际问题是我的 - 没有正确阅读文档,但是varargs api的潜在弱点导致了API的固有弱点 - 吸取的教训是阅读手册,并且非常非常小心在我的特定情况下从std::chrono::d uration类型对底层类型的任何自动类型转换。

最新更新