LibCurl连接复用原理

一. 背景描述

Curl是计算机中使用最多的网络请求工具,很多开源项目在内核中对于HTTP、FTP的操作是通过libcurl库获得的技术支持。这篇文章主要调研libcurl库中的TCP连接复用部分。

二. 连接池

Libcurl针对tcp连接采用了连接池管理,一次传输完成后,它将在“连接池”(有时也称为连接缓存)中保持N个连接处于活动状态,以便恰好能够重用现有连接之一的后续传输可以使用它而不是创建一个新连接。

重用一个连接而不是创建一个新的连接在速度和所需资源方面提供了显著的好处。

当libcurl准备建立一个新的连接来进行传输时,它首先会检查池中是否有可以重用的现有连接。**连接重用检查是在使用任何DNS或其他名称解析机制之前完成的,因此它完全基于主机名。**如果已经存在到正确主机名的实时连接,则还将检查许多其他属性(端口号,协议等),以确保可以使用它。

三. 连接池场景

Libcurl 几乎在所有的实现场景中都自动加入了连接池支持,主要的场景包括以下三种:

  1. Easy API pool
  2. Multi API pool
  3. Sharing the “connection cache”

3.1 Easy API pool

当您使用easy API,或更具体地说,使用curl_easy_perform()时,libcurl将使该池与特定的easy句柄关联。 然后重用同一简单句柄将确保它可以重用其连接。

3.2 Multi API pool

当您使用multi API时,连接池将与multi句柄相关联。这允许您自由地清理和重新创建easy句柄,而不会有丢失连接池的风险,并且允许一个easy句柄使用的连接在以后的传输中被另一个简单句柄重用。只需重用multi句柄。

3.3 Sharing the “connection cache”

从libcurl 7.57.0开始,应用程序可以使用 share interface,以使其他独立的传输共享同一连接池。

四. Curl-TCP长连接和DNS解析的关系

libcurl具有自己的内部DNS缓存,默认情况下它将在其中缓存解析的地址60秒(此选项可以更改) 。因此具有相同名称的后续解析将使用该时间范围内的缓存结果。

curl的连接缓存完全基于URL中使用的主机名,因此,如果缓存中已有与“ example.com”的可用连接,则该连接将用于对同一主机名的后续请求。 curl既不知道也不关心该名称的IP地址是什么,或者自连接启动以来它是否更改。 重用连接时,它将跳过整个dns解析阶段。

传输完成且连接仍然处于活动状态时,将把连接放回连接缓存中(或者,如果由于达到了限制而认为缓存已满,则关闭连接)。

由于连接重用是基于名称完成的,因此使用另一个名称解析为现有连接的相同IP不会使curl重复使用该连接。 它将解析名称并为此创建一个新的连接。

一个连接可以无限期地保留在连接缓存中,除非它被杀死以腾出空间或被重用。 如果它“死了”(由于它从另一端关闭),则当它被注意到时,它将最终从缓存中删除。

HTTP/2

可以通过HTTP/2发送的PING帧不会在连接缓存中处理(atm)连接,这将导致它们很快被服务器杀死。(libcurl 7.62.0添加了一个新的API,允许应用程序保持这样的连接,参见curl_easy_upkeep)

DoH

随着curl 7.62.0引入了DoH (dn -over- https)支持,DNS缓存将缓存TTL秒数的名称,而不仅仅是使用默认的60秒。

四. 重点总结

五. LibCurl 和 DNS解析的结合方案

由于从上面的内容可以看出,LibCurl和DNS解析之间没有动态感应的过程,所以如果需要增加DNS动态感应过程不能从LibCurl下手,而应该通过LibCurl的调用方增加一定的策略来下手,主要的策略包括以下三种:

  1. DNS TTL方案: 针对LibCurl库调用包装一层,增加一层域名的DNS TTL读取功能,根据TTL进行DNS请求定时器的设计,从而感应DNS的变化过程。但是需要注意的是TTL数据在标准的POSIX DNS解析 API中不可用,可以使用非dns完成名称解析。
  2. Libcurl Handle 增强控制方案:Libcurl库对外统一暴露了handle内存区,为了感应到dns变化,我们就必须要让handle强制进入到dns解析阶段,因此可以针对Libcurl handle内存区的存活时长 和 内存区域使用次数下手,当时间超过了最大存活时长 或者 handle使用次数超过了最大复用次数,则强行进行Libcurl-handle内存区域释放,从而促使Libcurl进入dns解析过程。

参考资料: