我有一个在powerpc上运行的跨平台嵌入式libCurl客户端应用程序,其行为与windows对应程序不同。基本问题是我的客户端上传文件以在返回226响应之前执行非常长的操作的远程服务器(表示成功上载)。远程FTP服务器此时实际执行闪回,此操作最多可能需要900秒。实际上,我正在尝试在等待远程226或错误响应时使用数据不活动超时。
在Windows上,这可以正常工作,但是在PowerPC嵌入式客户端上(我们链接到使用Mentor Graphics Code Sourcery工具链为PowerGNU编译的最新libCurl-7.39.0库),客户端在正常60秒的FTP不活动后超时。
我设置定时器的方式如下面的代码所示(请注意,我确保CURLOPT_FTP_RESPONSE_TIMEOUT的值比CURLOPT_TIMEOUT低1秒。另外,值得注意的是CURLOPT_CONNECTTIMEOUT设置为60秒(也许这是巧合,但是对于powerPC linux客户端上的超时不活动需要CURLOPT_CONNECTTIMEOUT(即60)秒。)我想知道CURLOPT_CONNECTTIMEOUT中是否存在一些潜在的错误覆盖或破坏了Linux客户端上的CURLOPT_FTP_RESPONSE_TIMEOUT?
除此之外,我的卷曲选项似乎工作正常。我读了一篇关于libCurl中定时器实现的article,其中看起来定时器是按照某种“先到期”顺序组织的,也许是在我更新默认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 :(得分:0)
实际上我发现了问题 - 结果主要是我的问题:
在我们的目标平台上进行大量调试和打印输出之后,事实证明,由于使用的弱点,该错误的来源是75%的应用程序问题(我的)和25%(在我看来)的libCurl问题松散耦合的va_args在设置libCurl选项时从可变长度参数列表中提取参数。这个问题与隐含的“long long”到“long”转换以及PowerPC平台上的Endian相关问题有关,这在Windows平台上不是问题。我使用libCurl来满足C ++应用程序中的FTP客户端需求 - 链接到标准模板C ++库。我使用std :: chrono :: seconds对象来设置时间和持续时间的libCurl选项。然而,在封面下,std :: chrono :: seconds是一个相当复杂的模板类型,内部表示为n 8字节PPC'long long',它不同于下面选项中硬编码的4字节PPC'long' 。由于传入'long long'参数和实际'long'之间的松耦合,CURLOPT_SERVER_RESPONSE_TIMEOUT中设置的值实际上是来自power PC平台上8字节'long long'的不正确的4个字节。我通过编写一段代码来证实这一点,以验证它在Windows上的工作方式,而不是我们的32位PPC嵌入式目标。
我在应用程序级别设置修复代码的方法是确保有一个与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;
libFurl用户论坛上的Dan Fandrich正确地指出:
CURLOPT_FTP_RESPONSE_TIMEOUT(原名 CURLOPT_SERVER_RESPONSE_TIMEOUT)记录需要很长时间。有 对此毫不含糊。由于curl_easy_setopt使用varargs,所以 除了在这种情况下投射,或与任何 curl_easy_setopt的其他参数与请求的不匹配 类型。我很高兴您在程序中找到了问题的根源,但是 正如curl_easy_setopt的手册页所说:
请仔细阅读本手册,因为错误的输入值可能导致libcurl 表现得很糟糕!
大多数LibCurl的维护者/作者Dan Steinberg回应了我的断言,即varargs是一个容易出现用户错误的弱api:
是的,使用varargs可能不是最明智的设计选择 当我们在大约14年前创建API时,它也是我们的原因 持续强调为每个选项传递的确切变量类型。
typecheck-gcc.h macromania是我们尝试帮助用户的另一种方式 发现这些错误。
总而言之,实际问题是我的 - 不是正确阅读文档,但是varargs api的潜在弱点导致了API的内在弱点 - 所学的经验是阅读手册并且非常小心任何在我的特定情况下,std :: chrono :: duration类型的底层类型的自动类型转换。