2014-12-06 138 views
0

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

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

予设定的定时器如示于下剪断的代码(注意,我确保CURLOPT_FTP_RESPONSE_TIMEOUT具有比CURLOPT_TIMEOUT 1秒较低的值。此外,值得注意的是,CURLOPT_CONNECTTIMEOUT被设定为所述的方式60秒(也许这是巧合,但是需要CURLOPT_CONNECTTIMEOUT(即60秒),以便在powerPC linux客户端上将非活动状态设为超时)。我想知道CURLOPT_CONNECTTIMEOUT中是否存在某个错误,它会覆盖或破坏Linux客户端上的CURLOPT_FTP_RESPONSE_TIMEOUT ?

除此之外,我的卷曲选项似乎工作正常。我读了一个article关于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

也许你还需要http://curl.haxx.se/libcurl/c/CURLOPT_ACCEPTTIMEOUT_MS.html如果你的数据传输使用被动ftp? – 2014-12-15 08:03:28

+0

如果您想要“空闲超时”,您应该设置“PROGRESSFUNCTION”并实施您自己的逻辑来确定连接是否卡住。 – 2014-12-15 08:04:58

回答

0

其实我发现这个问题 - 原来是大多我的问题:

我们的目标打印输出的多少调试洒后

平台,事实证明,由于使用松散耦合的va_args从可变长度参数列表中提取参数的弱点,错误的来源是75%的应用程序问题(我的)和25%(在我看来)libCurl问题设置libCurl opti附件。这个问题与一个隐含的“漫长的”到“长期”的转换有关,也是PowerPC平台上Endian相关的问题,这在Windows平台上不是问题。

我使用libCurl来满足我们的FTP客户端在C++应用程序中的需求 - 链接到标准模板C++库。我使用std :: chrono :: seconds对象来设置时间和持续时间libCurl选项。但是,在std :: chrono :: seconds中,std :: chrono :: seconds是一个相当复杂的模板类型,其内部表示形式为n 8字节的PPC'long long',它与4字节的PPC'long'不同,后者在以下选项。由于传递'long long'参数与实际'long'之间的松耦合,CURLOPT_SERVER_RESPONSE_TIMEOUT中设置的值实际上是来自Power平台上8字节'long long'的不正确4个字节。我通过编写一段代码来验证它是如何在Windows上工作的,而不是在我们的32位PPC嵌入式目标上进行验证。

我在应用程序级别设置固定代码的方式是确保显式转换为与va_arg第2个参数相同的类型 - 这是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; 

丹Fandrich上的libcurl用户论坛正确地指出:

CURLOPT_FTP_RESPONSE_TIMEOUT(原 CURLOPT_SERVER_RESPONSE_TIMEOUT)形成文件采取很长。有 没有含糊不清。由于curl_easy_setopt使用可变参数,除了在这种情况下投射外, 没有太多选择,或者与curl_easy_setopt的其他任何参数不匹配请求的 类型。我很高兴你发现在你的程序问题的根源,但 作为curl_easy_setopt手册页说:

仔细阅读本手册坏的输入值,可能引起的libcurl到 表现不好!

和丹·斯坦伯格,大部分的libcurl的维护者/作者回复了我的断言,可变参数是一个软弱的API,很容易出现用户错误:

呀,使用可变参数为这很可能不是我们在14年前创建API时最明智的设计选择 ,但这也是为什么我们 不断强调准确的变量类型传递给每个选项。

typecheck-gcc.h macromania是我们尝试帮助用户发现这些错误的另一种方式。

所以总结一下,实际的问题是我的 - 不能正确读取文档,但是可变参数API的潜在的弱点造成了API中固有的弱点 - 的教训是阅读手册,并非常非常在我的特殊情况下小心从std :: chrono :: duration类型的基础类型的任何自动类型转换。