2014-12-06 140 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類型的基礎類型的任何自動類型轉換。