情况:上传和下载大文件。
我偶尔会遇到一个代理,它会立即接受完整的请求(以完全的局域网速度),然后通过互联网慢慢转发。它通常是一些未知的公司代理,但今天我可以用BurpSuite代理模拟它。
为处理这种情况的CURL配置超时似乎很困难。 目前,我正在使用低速超时,因为我不知道完整传输需要多长时间(即我不能使用通常的CURLOPT_TIMEOUT)。
这种代理问题,整个请求在开始时发送(平均速度= 1.7e + 07 B / s),然后在十几秒左右后,平均速度降到阈值以下。 。超时!
大型下载情况再次不同。 一个小的GET请求进入代理,然后在将它传递到客户端之前先下载整个响应。我有时在现场看到这种情况,有很长的延迟,然后突然下载很快完成。
知道应该如何正确处理这类事情?
答案 0 :(得分:0)
除了curl的内置连接超时之外,您还必须编写自定义读取和写入超时。
这是使用curl_multi最简单的方法(您可以通过CURLOPT_PROGRESSFUNCTION
使用curl_easy编写解决方案,但我觉得这些东西太脆弱了我不喜欢。)
这是一个“阻塞读取”的代码,使用多接口(curl_data
是一个带有东西的自定义结构):
static bool curlPerform(struct curl_data* ctrl) {
int timeout = ctrl->timeout;
struct timespec timeoutTimestamp, currentTimestamp;
clock_gettime(CLOCK_MONOTONIC, &timeoutTimestamp);
int fdEvents = 0;
do {
if (*ctrl->interrupted) {
return false;
}
int running;
LOG("Invoking perform()");
CURLMcode result = curl_multi_perform(ctrl->multi, &running);
if (result) {
handleMultiError(ctrl, result);
return true;
}
if (checkResult(ctrl)) {
LOG("Has completed connections, bailing");
return true;
}
if (running == 0) {
LOG("No running connections, bailing");
return true;
}
if (*ctrl->interrupted) {
return false;
}
long waitTime;
curl_multi_timeout(ctrl->multi, &waitTime);
if (timeout < waitTime) {
waitTime = timeout;
}
LOG("Timeout is %d, waiting for %ld", timeout, waitTime);
if (waitTime > 0) {
CURLMcode waitResult = curl_multi_wait(ctrl->multi, NULL, 0, waitTime, &fdEvents);
if (waitResult) {
handleMultiError(ctrl, waitResult);
return true;
}
}
LOG("Sockets with events: %d", fdEvents);
if (fdEvents) {
timeout = ctrl->timeout;
} else {
clock_gettime(CLOCK_MONOTONIC, ¤tTimestamp);
timeout -= ((currentTimestamp.tv_sec - timeoutTimestamp.tv_sec) * 1000);
int diff_nsec = currentTimestamp.tv_nsec - timeoutTimestamp.tv_nsec;
if (diff_nsec < 0) {
timeout += 1000;
timeout -= ((diff_nsec + 1000000000) / 1000000);
}
if (timeout <= 0) {
LOG("Remaining time is %d, bailing", timeout);
throwTimeout(ctrl->env, ctrl->headerPairCount);
return true;
}
timeoutTimestamp = currentTimestamp;
}
} while (true);
}
上面的代码是this code的精简版,删除了大多数不相关的内容。它基本上在curl_multi之上实现了curl_easy,增加了在错误,连接和读取超时时退出的能力。
为了使代码适应您的需求,您必须实施两项小改动:
您可以提供CURLOPT_WRITEFUNCTION
/ CURLOPT_READFUNCTION
来跟踪请求/响应进度。一旦读取功能上传了所有数据,请停止强制执行写入超时(例如,通过将ctrl-&gt; timeout设置为INT_MAX
)。一旦写入功能记录第一个内容字节的到来,就开始强制执行读取超时。
答案 1 :(得分:0)
这是我最终使用的代码,所以我接受了
问题(起初)是大型上传速度非常快,然后在代理转发数据时暂停。这将在代理完成传输之前触发低速超时并返回响应。
因此,下面的代码会在上传完成后禁用低速超时,然后只需等待最终响应(不理想)。
但是,这对通过这个奇怪的代理下载没有帮助。
在完成下载之前,代理会下载但不会转发任何内容(除非每分钟一个字节)。因此,如果下载量大于约20MB,则低速超时会跳闸。
所以最后我完全禁用了所有超时,并且不确定我是否可以做任何更好的事情......
以下是代码,但请再次注意,它对大量下载没有帮助。 如果你将低速超时降低到每分钟大约1个字节或类似的东西,那可能没问题。
int progress_callback(void * clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
// a pseudo-member function, but its actually a static member for callback
NetConfig * self = static_cast<NetConfig*>(clientp);
// Now deal with the low-speed timeout.
// some proxies accept the whole transfer at once and then forward,
// giving the appearance of the connection "hung" at the end.
// We cannot turn off the low-speed limit when we have a proxy configured,
// because some sites have transparent proxies, so the plain network
// behaves in this fashion.
// IF we have finished the transfer, then disable the low speed timeout.
// we only do this in the Timeout_Slow mode
if (self->timeout_mode == Timeout_Slow)
{
bool want_lowspeed = (dltotal > 0 and dlnow < dltotal)
or (ultotal > 0 and ulnow < ultotal);
if (want_lowspeed != self->lowspeed_limit_enabled)
{
if (want_lowspeed)
{
curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_LIMIT, timeout_slow.first);
curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_TIME, timeout_slow.second);
}
else
{
curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_LIMIT, (long)0L);
curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_TIME, (long)0L);
}
self->lowspeed_limit_enabled = want_lowspeed;
}
}
return 0;
}