代码之家  ›  专栏  ›  技术社区  ›  elegant dice

libcurl的适当超时

  •  0
  • elegant dice  · 技术社区  · 7 年前

    情况:上传和下载大文件。

    似乎很难为CURL配置超时来处理这种情况。 目前,我正在使用低速超时,因为我不知道完整传输需要多长时间(即我不能使用通常的CURLOPT\u超时)。

    一个小的GET请求发送到代理,然后代理首先下载整个响应,然后再将其传递到客户端。我有时在现场看到这种情况,延迟很长时间,然后突然下载很快完成。

    你知道应该如何正确处理这种事情吗?

    2 回复  |  直到 7 年前
        1
  •  0
  •   user1643723    7 年前

    使用curl\u multi最容易做到这一点(您可以通过 CURLOPT_PROGRESSFUNCTION

    这是一个使用多接口的“阻塞读取”代码( curl_data 是包含stuff的自定义结构):

    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, &currentTimestamp);
    
            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\u multi的基础上轻松实现curl\u,并增加了在出现错误、连接和读取超时时退出的能力。

    为了使代码适应您的需要,您必须实现两个小更改:

    1. 将读取超时和写入超时分开;
    2. 仅在数据的第一个字节(而不是头)之后开始强制执行读取超时到达

    您可以通过提供 CURLOPT_WRITEFUNCTION / CURLOPT_READFUNCTION . 读取功能上载完所有数据后,停止强制执行写入超时(例如,将ctrl->timeout设置为 INT_MAX ). 一旦写入函数注册第一个内容字节的到达,开始强制执行读取超时。

        2
  •  0
  •   elegant dice    7 年前

    问题是(一开始)一个大的上传会很快进行,然后在代理转发数据时在最后暂停。这将在代理完成传输并返回响应之前触发低速超时。

    因此,下面的代码将在上传完成时禁用低速超时,然后永远等待最终响应(不理想)。

    但是,这对通过这个奇怪的代理下载没有帮助。

    代理将下载但不会转发任何内容,直到完成下载(可能每分钟一个字节除外)。因此,如果下载量大于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;
    }