news 2026/9/11 22:50:23

libcurl Share 接口实战:在多个 easy handle 之间共享 Cookie、DNS、SSL 会话与连接缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libcurl Share 接口实战:在多个 easy handle 之间共享 Cookie、DNS、SSL 会话与连接缓存

libcurl Share 接口实战:在多个 easy handle 之间共享 Cookie、DNS、SSL 会话与连接缓存

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

本篇技术指南围绕 curl 仓库中的 libcurl-share(3) 手册 展开,系统讲解 libcurl 共享接口(share interface)的设计目标、share 对象的完整生命周期、六类可共享数据(Cookie、DNS 缓存、SSL 会话、连接缓存、PSL、HSTS)的配置方法,以及多线程场景下互斥锁回调的编写规范。读完本文,你将掌握如何用curl_share_*系列 API 让一批 easy handle 共用同一份"数据底座",从而显著降低重复建连、重复握手和重复解析带来的开销。

一、share 接口解决什么问题

在 libcurl 的编程模型中,每个CURL *easy handle默认都持有自己私有的 Cookie 数据库、DNS 缓存、TLS 会话缓存与连接缓存。当你的程序需要频繁向同一批主机发起大量小请求时,每个独立 handle 都会各自重新做 DNS 解析、重新建立 TCP/TLS 连接、重新走一次完整的握手流程,造成明显的重复开销。

share 接口正是为此而生。按 libcurl-share(3) 的 OBJECTIVES 一节所述:

The share interface was added to enable sharing of data between curl handles.

其核心场景是"一套数据,多次传输"(ONE SET OF DATA - MANY TRANSFERS):多个 easy handle 可以读写同一个Cookie 数据库、DNS 缓存、TLS 会话缓存和连接缓存,每一个单独传输都能直接受益于其它传输产生的数据更新——例如第一个 handle 完成的 TLS 握手,其会话票据可以被后续 handle 直接复用。

二、share 对象生命周期:init → setopt → cleanup

share 接口的全部函数都以curl_share前缀命名(见 libcurl-share(3) 的 DESCRIPTION),共三个公开 API:

函数职责详细手册
curl_share_init()创建并返回一个 share 句柄(CURLSH *curl_share_init(3)
curl_share_setopt()设置共享的数据类型、锁回调、用户数据curl_share_setopt(3)
curl_share_cleanup()销毁 share 对象并释放其持有的所有缓存curl_share_cleanup(3)

2.1 创建:curl_share_init()

#include <curl/curl.h> CURLSH *curl_share_init();

该函数返回一个指向CURLSH句柄的指针,作为其它所有 share 函数的入参(文档中也常称其为 share handle)。调用成功返回非 NULL 指针;返回 NULL 表示出错(如内存不足),share 对象未被创建。

从源码看,lib/curl_share.c 中curl_share_init()会:

  • 分配并清零struct Curl_share,设置 magic 标记CURL_GOOD_SHARE
  • CURL_LOCK_DATA_SHARE位写入specifier位图(内部用于对 share 自身状态的锁定);
  • 初始化 DNS 缓存Curl_dnscache_init(&share->dnscache, 23)
  • 创建一个内部的"admin" easy handle 用于管理共享连接缓存(share->admin = curl_easy_init())。

文档明确要求:curl_share_init()的每一次调用必须在全部使用该 share 的操作结束后,配对调用一次curl_share_cleanup()

2.2 配置:curl_share_setopt()

CURLSHcode curl_share_setopt(CURLSH *share, CURLSHoption option, parameter);

第二个参数optionCURLSHoption枚举,可用的选项定义在 include/curl/curl.h:

typedef enum { CURLSHOPT_NONE, /* do not use */ CURLSHOPT_SHARE, /* specify a data type to share */ CURLSHOPT_UNSHARE, /* specify which data type to stop sharing */ CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ CURLSHOPT_UNLOCKFUNC, /* pass in a 'curl_unlock_function' pointer */ CURLSHOPT_USERDATA, /* pass in a user data pointer used in the lock/unlock callback functions */ CURLSHOPT_LAST /* never use */ } CURLSHoption;
  • CURLSHOPT_SHARE:向 share 对象注册一类要共享的数据(类型见下一节)。
  • CURLSHOPT_UNSHARE:让 share 对象停止共享某一类数据。
  • CURLSHOPT_LOCKFUNC/CURLSHOPT_UNLOCKFUNC:设置互斥锁回调(多线程必需)。
  • CURLSHOPT_USERDATA:设置一个私有指针,原样传递给锁/解锁回调(libcurl 自身不使用它)。

一个重要约束(在 CURLSHOPT_SHARE(3) 与 CURLSHOPT_UNSHARE(3) 中均有明确警告):不要在 share 对象正在被使用时增删数据类型,只能在两次传输之间(between transfers)进行。对应源码实现中,curl_share_setopt()首先检查share_in_use()——如果已有 easy handle 通过引用计数持有该 share(ref_count > 1),直接返回CURLSHE_IN_USE(见 lib/curl_share.c)。

2.3 销毁:curl_share_cleanup()

CURLSHcode curl_share_cleanup(CURLSH *share_handle);
  • 删除 share 对象,调用后该句柄不可再使用
  • 如果该 share 仍被任何 easy handle 使用,调用会失败(返回CURLSHE_IN_USE),对象不会被删除;
  • 传入 NULL 时函数立即返回,不做任何操作;
  • 函数返回后再使用该 share 句柄属于非法行为(未定义行为)。

源码层面,curl_share_cleanup()会检查share_in_use(),然后调用share_unlink()递减引用计数,当引用计数归零时才真正执行share_destroy()(见 lib/curl_share.c)。share_destroy()会依次销毁连接池(Curl_cpool_destroy)、DNS 缓存(Curl_dnscache_destroy)、Cookie 数据库(Curl_cookie_cleanup)、HSTS 缓存(Curl_hsts_cleanup)、SSL 会话缓存(Curl_ssl_scache_destroy)与 PSL 数据(Curl_psl_destroy),最后释放内部 admin handle(见 lib/curl_share.c)。

多线程销毁特别提醒(curl_share_cleanup(3) 原文强调):当 share 在多个线程中被使用时,销毁动作只能发生在其它所有线程都已停止使用它之后。libcurl 只能统计有多少个 easy handle 正在引用该 share,无法感知你的应用还持有多少个指向该 share 的指针——所以销毁时机的最终责任在应用代码。

三、可共享的六类数据:CURL_LOCK_DATA_*

CURLSHOPT_SHAREtype参数取值为curl_lock_data枚举,完整定义在 include/curl/curl.h:

typedef enum { CURL_LOCK_DATA_NONE = 0, /* CURL_LOCK_DATA_SHARE is used internally to say that the locking is made * to change the internal state of the share itself. */ CURL_LOCK_DATA_SHARE, CURL_LOCK_DATA_COOKIE, CURL_LOCK_DATA_DNS, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_PSL, CURL_LOCK_DATA_HSTS, CURL_LOCK_DATA_LAST } curl_lock_data;

其中CURL_LOCK_DATA_NONECURL_LOCK_DATA_SHARE为内部保留值,应用可配置的六类数据及其语义如下:

类型共享内容版本线程支持
CURL_LOCK_DATA_COOKIECookie 数据库7.10.3不支持多线程并发共享
CURL_LOCK_DATA_DNS解析后的 DNS 主机缓存7.10.3需互斥锁回调
CURL_LOCK_DATA_SSL_SESSIONTLS 会话(减少重复握手)7.10.3需互斥锁回调
CURL_LOCK_DATA_CONNECT连接缓存(连接池)7.10.3不支持多线程并发共享
CURL_LOCK_DATA_PSLPublic Suffix List(公共后缀列表)7.61.0需互斥锁回调
CURL_LOCK_DATA_HSTS内存中的 HSTS 缓存7.88.0不支持多线程并发共享

3.1 CURL_LOCK_DATA_COOKIE:共享 Cookie 数据库

所有绑定该 share 的 easy handle 读写同一份Cookie 数据库。需要注意两点(见 CURLSHOPT_SHARE(3)):

  • 共享 Cookie不会自动激活 easy handle 的 Cookie 引擎,你仍需单独通过CURLOPT_COOKIEFILE等选项启用;
  • 官方明确不支持在多个并发线程间共享 Cookie。

一个容易忽略的行为(来自 CURLOPT_SHARE(3)):当你把一个共享了 Cookie 的 share 绑定到 easy handle 时,该 handle 的 Cookie 引擎会被自动启用data->cookies = share->cookies);反过来,当你解绑(或换绑到不共享 Cookie 的 share)时,Cookie 引擎会被自动禁用。对应实现见 lib/curl_share.c 的Curl_share_easy_link()

3.2 CURL_LOCK_DATA_DNS:共享 DNS 缓存

缓存的主机解析结果在所有绑定该 share 的 easy handle 间共享。文档特别提示:当使用 multi 接口时,同一 multi handle 下的所有 easy handle 默认就共享 DNS 缓存,无需此选项。

3.3 CURL_LOCK_DATA_SSL_SESSION:共享 TLS 会话

SSL 会话在所有绑定该 share 的 easy handle 间共享,重连同一服务器时可显著缩短 TLS 握手耗时。同样地,multi 接口下同一 multi handle 的 easy handle 默认共享 SSL 会话缓存。从实现看,共享的 SSL 会话缓存由Curl_ssl_scache_create(25, 2, ...)创建(25 个会话槽位的容量限制,见 lib/curl_share.c)。

3.4 CURL_LOCK_DATA_CONNECT:共享连接缓存

将连接缓存放入 share 对象,让所有绑定它的 easy handle 共享连接池——这是提升重复请求性能最显著的一类共享。文档还给出了三条额外说明:

  • 不支持多线程间共享连接;
  • HTTP/2 与 HTTP/3 多路复用:只有当现有连接由同一个 multi 或 easy handle持有时,才会把新传输附加到该连接上;libcurl 不支持用共享连接在不同线程间做多路复用流;
  • 连接数量限制:CURLMOPT_MAX_HOST_CONNECTIONSCURLMOPT_MAX_TOTAL_CONNECTIONS对使用共享连接缓存的传输同样生效,每个传输会把它所在 multi handle 的限制施加到共享缓存上(详见 CURLMOPT_MAX_HOST_CONNECTIONS(3) 与 CURLMOPT_MAX_TOTAL_CONNECTIONS(3))。

实现上,CURL_LOCK_DATA_CONNECT被设置为"可以重复设置"(It is safe to set this option several times on a share),内部通过Curl_cpool_init(&share->cpool, share, 103)初始化连接池(见 lib/curl_share.c)。

3.5 CURL_LOCK_DATA_PSL:共享 Public Suffix List

share 对象中保存的 PSL 数据对所有绑定的 easy handle 可用。由于 PSL 会周期性刷新,共享它可避免在过多不同上下文中各自维护更新。同样,multi 接口下同一 multi handle 默认共享 PSL。该类型要求 libcurl 以USE_LIBPSL编译,否则curl_share_setopt()返回CURLSHE_NOT_BUILT_IN(见 lib/curl_share.c)。

3.6 CURL_LOCK_DATA_HSTS:共享 HSTS 缓存

共享内存中的 HSTS(HTTP Strict Transport Security)缓存,不支持多线程并发共享。该类型同样有编译开关约束:libcurl 若以CURL_DISABLE_HSTS构建则返回CURLSHE_NOT_BUILT_IN

3.7 停止共享:CURLSHOPT_UNSHARE

CURLSHOPT_SHARE一一对应,CURLSHOPT_UNSHARE用于让 share 对象停止共享某类数据,例如:

sh = curl_share_setopt(share, CURLSHOPT_UNSHARE, CURL_LOCK_DATA_COOKIE);

可以多次调用以移除多类数据,之后仍可用CURLSHOPT_SHARE重新加回。同样只能"在两次传输之间"操作,不能在使用中移除。

四、把 easy handle 绑定到 share:CURLOPT_SHARE

创建并配置好 share 对象后,通过curl_easy_setoptCURLOPT_SHARE选项把它绑定到任意数量的 easy handle 上:

CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SHARE, CURLSH *share);

关键语义(见 CURLOPT_SHARE(3)):

  • 绑定的 share 必须由curl_share_init()创建;
  • 设置后,该 handle 对"share 已声明共享"的那部分数据改为使用共享副本,其余未共享的数据仍按常规方式独立管理("Data that the share object is not set to share is dealt with the usual way, as if no share was used");
  • 默认值为 NULL(不共享);
  • 想解除绑定,把CURLOPT_SHARE设为 NULL 即可。警告:在传输进行中把 share 设置为 NULL 是不被推荐的,可能导致未定义行为;
  • 如果多个线程同时使用这些 handle,则必须给 share 配置锁回调(见下一节)。

从源码看,绑定动作最终走到 lib/curl_share.c 的Curl_share_easy_link():递增引用计数(share_ref_inc)、建立data->share = share关联,并把 Cookie/HSTS/PSL 指针从 share 复制到 handle 上;解绑对应Curl_share_easy_unlink()(lib/curl_share.c),若共享了连接缓存还会先Curl_detach_connection()摘除当前连接。

五、多线程使用:LOCKFUNC / UNLOCKFUNC 互斥锁回调

libcurl内部没有线程同步机制。因此只要 share 会被多线程并发访问,就必须由应用提供锁/解锁回调,通过curl_share_setopt()注册。

回调函数原型定义在 include/curl/curl.h:

typedef void (*curl_lock_function)(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr); typedef void (*curl_unlock_function)(CURL *handle, curl_lock_data data, void *userptr);

参数含义(见 CURLSHOPT_LOCKFUNC(3) 与 CURLSHOPT_UNLOCKFUNC(3)):

  • handle:当前正在使用该 share 的 easy handle;
  • data:libcurl 想锁/解锁的数据类型(curl_lock_data),建议对每种数据类型使用不同的锁,避免不必要的串行化;
  • locktype(仅 lock 回调):访问类型,取值为CURL_LOCK_ACCESS_SHARED(读)或CURL_LOCK_ACCESS_SINGLE(写),枚举定义见 include/curl/curl.h;
  • userptr:通过CURLSHOPT_USERDATA设置的私有指针,libcurl 原样透传、绝不使用。

设置示例:

extern void mutex_lock(CURL *handle, curl_lock_data data, curl_lock_access access, void *clientp); sh = curl_share_setopt(share, CURLSHOPT_LOCKFUNC, mutex_lock); sh = curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, mutex_unlock); sh = curl_share_setopt(share, CURLSHOPT_USERDATA, &private_stuff);

内部实现中,共享数据的每次读写都会经由 lib/curl_share.c 的Curl_share_lock_share()/Curl_share_unlock_share()转发到应用回调;只有当该数据类型确实被共享(specifier位图中对应位为 1)时才调用锁回调,未共享的类型则直接返回CURLSHE_OK装作加锁成功。

特别注意:即便配了锁回调,以下三类数据官方仍明确不支持多线程并发共享:CURL_LOCK_DATA_COOKIECURL_LOCK_DATA_CONNECTCURL_LOCK_DATA_HSTS

六、完整实战示例:跨 handle 共享连接缓存

仓库的 docs/examples/shared-connection-cache.c 提供了一个可直接编译运行的完整示例:循环中反复创建并销毁 easy handle,但因为连接池在 share 对象里,连接得以跨 handle 复用:

#include <stdio.h> #include <curl/curl.h> static void my_lock(CURL *curl, curl_lock_data data, curl_lock_access laccess, void *useptr) { (void)curl; (void)data; (void)laccess; (void)useptr; fprintf(stderr, "-> Mutex lock\n"); } static void my_unlock(CURL *curl, curl_lock_data data, void *useptr) { (void)curl; (void)data; (void)useptr; fprintf(stderr, "<- Mutex unlock\n"); } int main(void) { CURLSH *share; int i; CURLcode result = curl_global_init(CURL_GLOBAL_ALL); if(result != CURLE_OK) return (int)result; share = curl_share_init(); curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); curl_share_setopt(share, CURLSHOPT_LOCKFUNC, my_lock); curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, my_unlock); /* 每轮都新建并销毁 easy handle,但连接池在 share 对象中,连接仍被复用 */ for(i = 0; i < 3; i++) { CURL *curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se/"); curl_easy_setopt(curl, CURLOPT_SHARE, share); /* 使用共享对象 */ result = curl_easy_perform(curl); if(result != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(result)); curl_easy_cleanup(curl); } } curl_share_cleanup(share); curl_global_cleanup(); return (int)result; }

代码骨架中同时展示了CURLSHOPT_LOCKFUNCCURLSHOPT_UNLOCKFUNC的注册方式(本例为单线程,锁回调仅打印日志;多线程下请替换为真实的互斥量实现)。

再结合 CURLOPT_SHARE(3) 的示例,两个 handle 共享 Cookie 的典型写法:

CURLSH *shobject = curl_share_init(); curl_share_setopt(shobject, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); /* 第一个 handle:登录/首次请求,写入共享 Cookie 库 */ curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* 启用 Cookie 引擎 */ curl_easy_setopt(curl, CURLOPT_SHARE, shobject); curl_easy_perform(curl); curl_easy_cleanup(curl); /* 第二个 handle:直接复用第一个 handle 写入的 Cookie */ curl_easy_setopt(curl2, CURLOPT_URL, "https://example.com/second"); curl_easy_setopt(curl2, CURLOPT_COOKIEFILE, ""); curl_easy_setopt(curl2, CURLOPT_SHARE, shobject); curl_easy_perform(curl2); curl_easy_cleanup(curl2); curl_share_cleanup(shobject);

七、错误处理:CURLSHcode 返回值

curl_share_init()之外的两个函数都返回CURLSHcode枚举(定义见 include/curl/curl.h):

返回值含义
CURLSHE_OK0操作成功
CURLSHE_BAD_OPTION1选项或数据类型无效
CURLSHE_IN_USE2share 仍被使用中,无法设置选项或销毁
CURLSHE_INVALID3share 句柄非法
CURLSHE_NOMEM4内存不足
CURLSHE_NOT_BUILT_IN5功能未编入当前 libcurl 构建(如禁用 Cookie/HSTS/SSL 或未启用 libpsl)
CURLSHE_LAST-哨兵值,不要使用

完整的错误码说明见 libcurl-errors(3)。CURLSHE_NOT_BUILT_IN尤其值得注意:它直接反映了 lib/curl_share.c 中按编译宏(CURL_DISABLE_HTTPCURL_DISABLE_COOKIESCURL_DISABLE_HSTSUSE_SSLUSE_LIBPSL)逐个校验共享类型的分支逻辑——即使 API 存在,某些共享能力也取决于你的构建配置。

八、使用约束与注意事项汇总

综合 libcurl-share(3) 及其关联手册,整理出以下必须遵守的约束:

  1. 生命周期配对curl_share_init()必须配对curl_share_cleanup(),且销毁前必须确认没有任何 easy handle 仍在使用该 share(否则返回CURLSHE_IN_USE)。
  2. 配置时机CURLSHOPT_SHARE/CURLSHOPT_UNSHARE只能在传输之间调用,share 使用中设置会返回CURLSHE_IN_USE
  3. 多线程三必须:多线程共享时必须设置CURLSHOPT_LOCKFUNC+CURLSHOPT_UNLOCKFUNC;推荐对每种数据类型使用独立锁;CURLSHOPT_USERDATA用来向回调传递你的锁对象或上下文。
  4. 三类数据禁多线程:Cookie、连接缓存、HSTS 官方不支持跨线程并发共享;HTTP/2 与 HTTP/3 的多路复用流也不支持跨线程共享连接。
  5. 解绑时机:不要在传输进行中把CURLOPT_SHARE设为 NULL,属于未定义行为。
  6. multi 接口的默认行为:同一 multi handle 下的 easy handle 默认共享 DNS 缓存、SSL 会话缓存、连接缓存与 PSL,无需显式使用 share 接口;share 接口主要价值在于让不同multi(或不同时刻创建)的 easy handle 之间共享数据。

更宏观的多线程注意事项可进一步参阅 libcurl-thread(3);share 接口与 easy 接口、multi 接口的配合关系见 libcurl-easy(3) 与 libcurl-multi(3)。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 22:48:24

校园外卖跑腿系统设计与实现:Android四端架构与订单状态机解析

简介&#xff1a;基于Android的校园外卖跑腿系统毕业设计项目&#xff0c;覆盖用户端、商家端、骑手端、管理员端四个角色模块&#xff0c;实现从用户点餐、订单支付、商家接单、骑手派送到后台管理的完整业务闭环&#xff0c;面向计算机相关专业毕业生及Android/Java课程设计学…

作者头像 李华
网站建设 2026/9/11 22:47:14

用Python和FastAPI打造超市管理系统:表设计、事务与并发控制

简介&#xff1a;一份基于Python开发的超市管理系统毕业设计资料包&#xff0c;面向计算机相关专业在校学生&#xff0c;可用于毕业设计、课程设计、项目初期演示&#xff0c;也适合新手学习Python项目从设计到落地的完整流程。资源覆盖系统设计、功能实现与文档整理&#xff0…

作者头像 李华
网站建设 2026/9/11 22:45:51

跨境电商运营是什么?核心逻辑、关键指标与 2026 最新趋势

摘要&#xff1a;跨境电商运营说简单点&#xff0c;就是把产品卖到海外、并让这门生意持续赚钱的一整套动作。本文讲清它的完整链路、核心指标&#xff0c;以及 2026 年全链路数字化、海外仓本地化、品牌化升级这三个正在发生的关键变化&#xff0c;帮你建立系统认知。 跨境电…

作者头像 李华
网站建设 2026/9/11 22:44:50

MCU与Linux嵌入式开发的分水岭:资源、耦合、交付三维度决策

1. 这个问题背后藏着三个被忽略的现实分水岭刚进芯片公司那会儿&#xff0c;我带的第一个实习生坐在我工位旁边&#xff0c;盯着电脑屏幕发呆。他刚把STM32F407的LED闪烁例程跑通&#xff0c;兴奋地截图发朋友圈&#xff0c;结果第二天就被主管叫去改Linux内核驱动——因为产线…

作者头像 李华