libcurl动态超时的陷阱

在《如何设置libcurl的动态超时值》中提到,动态超时值(通过CURLOPT_LOW_SPEED_LIMITCURLOPT_LOW_SPEED_TIME设置)比固定超时值(通过CURLOPT_TIMEOUT设置)更符合实际需要,所以我一般只设置动态超时值,而不设置固定超时值。然而,在实际使用中发现,有时网络请求会一直没有结束,也就是说设置的动态超时值在某些情况下会失效。在阅读了libcurl的源代码之后,我发现原来动态超时值存在一个陷阱。

简单地说,libcurl只在下载响应数据阶段才会检测动态超时,如果网络请求一直没有进入这个阶段,那么动态超时值就没有作用了。下面我们从libcurl的内部机制分析一下这个陷阱。

在libcurl内部,网络请求由Session对象表示,而Session对象由Multi对象管理。Multi对象是一个管理器,它自身维护一个定时器,每隔一段时间触发一次Session对象执行。也就是说,Session本身不会主动执行,而是由Multi来驱动执行, 如下图所示:

Session的实现是一个状态机,从请求开始到结束,大致要经过以下状态(这是一份精简过的状态,实际状态比这个复杂):

  • Init,初始化状态。
  • Connect,正在连接状态。
  • Do,正在发送请求数据状态。
  • WaitPerform,等待响应状态。
  • Perform,正在接收响应数据状态。
  • Completed,完成状态。

一般情况下,只有当Session处于Perform状态时,libcurl才会检测动态超时。所以,一旦等不到服务器的响应,Session就会一直停留在WaitPerform状态,动态超时值也就没有任何作用了。

至于固定超时值,它的检测时机是在每次心跳触发Session对象执行的时候,与Session的状态机制没有任何关系,因此固定超时值总是有效的。

可见,licurl的超时机制并不够好——动态超时值不可靠,固定超时值又不能满足需求。那么在实践中应该如何做呢?有两种方法可供选择。首先,如果条件允许的话,可以摒弃libcurl的超时机制,自己来实现。这样的好处是可以自主控制超时,具有最大的灵活性。当然,这种方法复杂度较高,大部分情况下条件也不允许。

其次,可以通过不断设置固定超时值来模拟动态超时。例如,每收到libcurl的一次进度回调,就重新设置一个更大的固定超时值。虽然官方文档并没有明说,但从libcurl的源代码来看,这种做法是可行的。