curl 共享对象取消共享详解:CURLSHOPT_UNSHARE 使用指南与实现原理
【免费下载链接】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
导读:CURLSHOPT_UNSHARE 是 libcurl 共享对象(share object)机制中与 CURLSHOPT_SHARE 对应的"反向操作",用于在运行时将 Cookie、DNS 缓存、SSL 会话、连接缓存等数据从共享状态中解除,恢复各 easy handle 的独立数据视图。本文基于 curl 官方文档与 libcurl 源码,讲解该选项的语义、五种可取消共享的数据类型、与 CURLSHOPT_SHARE 的配合用法、底层实现细节及适用场景,帮助读者在需要动态调整共享策略时安全、正确地使用它。
一、什么是 CURLSHOPT_UNSHARE
CURLSHOPT_UNSHARE 是 libcurl 共享对象(share object)机制中与 CURLSHOPT_SHARE 对应的"反向操作",用于在运行时将 Cookie、DNS 缓存、SSL 会话、连接缓存等数据从共享状态中解除,恢复各 easy handle 的独立数据视图。
共享对象是 libcurl 中一套独立于 easy handle 的生命周期管理机制,通过 curl_share_init(3) 创建,通过 curl_share_setopt(3) 配置,而 CURLSHOPT_SHARE 与 CURLSHOPT_UNSHARE 正是其中控制"共享什么数据"的一对开关:前者把某类数据加入共享,后者把某类数据从共享中移除。
函数原型
#include <curl/curl.h> CURLSHcode curl_share_setopt(CURLSH *share, CURLSHOPT_UNSHARE, int type);- share:由 curl_share_init() 创建的有效共享对象句柄;
- type:要停止共享的数据类型,必须是下文列出的
CURL_LOCK_DATA_*枚举值之一; - 返回值:
CURLSHE_OK(零)表示设置成功,非零表示出错,详见 libcurl-errors(3)。
从签名可以看出,CURLSHOPT_UNSHARE 与 CURLSHOPT_SHARE 是同一个函数curl_share_setopt()的两个选项,二者通过第二个参数区分,第三个参数统一为int type。该选项自7.10.3版本加入,适用于所有协议。
与 CURLSHOPT_SHARE 的对称关系
在 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(3) 文档中明确指出:"Unset a type again by setting CURLSHOPT_UNSHARE(3)"(通过设置 CURLSHOPT_UNSHARE 来取消某类数据的共享),反之亦然。这印证了两者是一对可反复切换的状态开关。
二、可取消共享的数据类型
type参数的可选值定义在 include/curl/curl.h 的curl_lock_data枚举中:
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_NONE、CURL_LOCK_DATA_SHARE为内部保留值,CURL_LOCK_DATA_LAST为哨兵值,实际可供应用层使用的有 6 个。CURLSHOPT_UNSHARE 文档明确支持其中 5 个(HSTS 仅在 CURLSHOPT_SHARE 中列出),逐一说明如下:
CURL_LOCK_DATA_COOKIE
停止共享 Cookie 数据。取消后,使用该共享对象的 easy handle 将不再共享彼此之间的 Cookie 状态,每个 easy handle 回到各自独立的 Cookie 处理逻辑。
从源码 lib/curl_share.c 看,取消共享时若共享对象中已存在 Cookie 仓库,会执行清理并置空:
case CURL_LOCK_DATA_COOKIE: #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies) { Curl_cookie_cleanup(share->cookies); share->cookies = NULL; } #else /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ res = CURLSHE_NOT_BUILT_IN; #endif break;注意:若 libcurl 在编译时被禁用 HTTP 或禁用 Cookie 支持(CURL_DISABLE_HTTP/CURL_DISABLE_COOKIES),该操作会返回CURLSHE_NOT_BUILT_IN。
CURL_LOCK_DATA_DNS
停止共享 DNS 缓存。取消后,各 easy handle 将各自维护独立的 DNS 解析缓存。
从源码看,DNS 类型在 UNSHARE 分支中是一个"空操作"(lib/curl_share.c),因为它没有独立的资源句柄需要销毁——共享 DNS 时仅通过specifier标志位记录共享意图(lib/curl_share.c),取消共享时同样只需清除标志位。DNS 缓存的销毁统一在share_destroy()中通过Curl_dnscache_destroy()完成(lib/curl_share.c)。
值得补充的是:当使用 multi 接口时,加入同一 multi handle 的所有 easy handle默认就共享 DNS 缓存,无需也不依赖本选项。
CURL_LOCK_DATA_SSL_SESSION
停止共享 SSL 会话缓存。取消后,各 easy handle 不再复用彼此缓存的 TLS 会话,重新建立 SSL 连接时需再次执行完整握手。
源码实现(lib/curl_share.c)会销毁共享的 SSL 会话缓存:
case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL if(share->ssl_scache) { Curl_ssl_scache_destroy(share->ssl_scache); share->ssl_scache = NULL; } #else res = CURLSHE_NOT_BUILT_IN; #endif break;若 libcurl 编译时未启用任何 TLS 后端(未定义USE_SSL),同样返回CURLSHE_NOT_BUILT_IN。
CURL_LOCK_DATA_CONNECT
停止共享连接缓存。取消后,各 easy handle 不再复用彼此缓存的 TCP/TLS 连接。
源码实现(lib/curl_share.c)中,连接缓存类型在 UNSHARE 分支同样是空操作——连接池本身在share_destroy()中通过Curl_cpool_destroy()统一销毁(lib/curl_share.c),取消共享仅清除specifier标志位。
连接缓存的共享有更严格的约束:不支持在多个并发线程之间共享连接;HTTP/2 与 HTTP/3 的多路复用流只有在连接被同一 multi 或 easy handle 持有时才会追加新传输,libcurl 不支持跨线程通过共享连接做多路复用。
CURL_LOCK_DATA_PSL
停止共享 Public Suffix List(公共后缀列表)。PSL 用于识别 Cookie 的作用域边界(如区分example.com与com),取消共享后各 easy handle 回退到各自的 PSL 上下文。
源码实现(lib/curl_share.c)中,PSL 在 UNSHARE 分支也是空操作;需要说明的是,若 libcurl 编译时未启用 libpsl(未定义USE_LIBPSL),即使在 SHARE 分支也会返回CURLSHE_NOT_BUILT_IN(lib/curl_share.c)。
需要特别提醒:在 CURLSHOPT_UNSHARE 的实现中,CURL_LOCK_DATA_PSL与CURL_LOCK_DATA_HSTS均落入default分支并返回CURLSHE_BAD_OPTION(lib/curl_share.c),因为文档明确列出的可取消类型仅包含 COOKIE、DNS、SSL_SESSION、CONNECT、PSL 五种。HSTS(CURL_LOCK_DATA_HSTS,7.88.0 加入,仅出现在 CURLSHOPT_SHARE(3) 中)当前不通过 UNSHARE 取消——若需停止共享 HSTS,应通过 curl_share_cleanup(3) 销毁整个共享对象,或调整使用该共享对象的 easy handle 集合。
三、使用约束:不要在共享对象"使用中"时取消共享
CURLSHOPT_UNSHARE 文档明确警告:
Do not remove types from a shared object that is being in use. Unshare them only between transfers.
不要在正在使用中的共享对象上移除数据类型,只能在两次传输之间执行 UNSHARE。原因是共享对象内部通过引用计数跟踪使用状态——lib/curl_share.c 中share_ref_inc()/share_ref_dec()维护ref_count,share_in_use()(lib/curl_share.c)判断ref_count > 1即视为使用中。
更关键的是,curl_share_setopt()的入口处直接执行了检查(lib/curl_share.c):
if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share_in_use(share)) { /* do not allow setting options while one or more handles are already using this share */ return CURLSHE_IN_USE; }也就是说,只要还有 easy handle 通过CURLOPT_SHARE绑定该共享对象,任何curl_share_setopt()调用(包括 SHARE 与 UNSHARE)都会返回CURLSHE_IN_USE。这一点在测试 tests/libtest/lib506.c 中有直接验证——测试先让 easy handle 使用共享对象,再调用curl_share_cleanup()期望其失败并打印 "SHARE_CLEANUP failed, correct"。
因此正确的取消共享流程必须是:
- 先确保所有使用该共享对象的 easy handle 完成当前传输并解绑(
curl_easy_cleanup()或不再使用); - 再调用
curl_share_setopt(share, CURLSHOPT_UNSHARE, type)取消共享; - 之后创建的 easy handle 将不再共享该数据类型,如需恢复则重新调用
CURLSHOPT_SHARE。
四、完整示例与错误处理
CURLSHOPT_UNSHARE 文档给出的最小示例:
int main(void) { CURLSHcode sh; CURLSH *share = curl_share_init(); sh = curl_share_setopt(share, CURLSHOPT_UNSHARE, CURL_LOCK_DATA_COOKIE); if(sh) printf("Error: %s\n", curl_share_strerror(sh)); }下面是一个更完整、可运行的实战示例——先共享 Cookie 与 DNS,再在两个传输之间取消 Cookie 共享:
#include <stdio.h> #include <curl/curl.h> int main(void) { CURLSHcode sh; CURLSH *share; CURL *easy; curl_global_init(CURL_GLOBAL_DEFAULT); share = curl_share_init(); if(!share) return 1; /* 第一步:开启共享 */ sh = curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); if(sh) { printf("SHARE cookie failed: %s\n", curl_share_strerror(sh)); goto cleanup; } sh = curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); if(sh) { printf("SHARE dns failed: %s\n", curl_share_strerror(sh)); goto cleanup; } /* 第二步:创建 easy handle 并绑定共享对象,执行传输 */ easy = curl_easy_init(); if(!easy) { curl_share_cleanup(share); return 1; } curl_easy_setopt(easy, CURLOPT_SHARE, share); curl_easy_setopt(easy, CURLOPT_URL, "https://example.com/"); curl_easy_perform(easy); curl_easy_cleanup(easy); /* 解绑,共享对象回到"空闲"状态 */ /* 第三步:两次传输之间,取消 Cookie 共享(DNS 继续保持共享) */ sh = curl_share_setopt(share, CURLSHOPT_UNSHARE, CURL_LOCK_DATA_COOKIE); if(sh) printf("UNSHARE cookie failed: %s\n", curl_share_strerror(sh)); /* 后续创建的 easy handle 将独立维护 Cookie,但 DNS 仍共享 */ cleanup: curl_share_cleanup(share); curl_global_cleanup(); return 0; }返回值与错误码
返回值类型为CURLSHcode,定义于 include/curl/curl.h:
typedef enum { CURLSHE_OK, /* all is fine */ CURLSHE_BAD_OPTION, /* 1 */ CURLSHE_IN_USE, /* 2 */ CURLSHE_INVALID, /* 3 */ CURLSHE_NOMEM, /* 4 out of memory */ CURLSHE_NOT_BUILT_IN, /* 5 feature not present in lib */ CURLSHE_LAST /* never use */ } CURLSHcode;调用 CURLSHOPT_UNSHARE 时可能遇到的错误码及典型触发条件:
| 错误码 | 含义 | 典型触发场景 |
|---|---|---|
CURLSHE_OK | 设置成功 | 正常取消共享 |
CURLSHE_BAD_OPTION | 非法选项 | type不是受支持的CURL_LOCK_DATA_*值(如传入 HSTS、PSL) |
CURLSHE_IN_USE | 共享对象正在使用中 | 仍有 easy handle 绑定该共享对象时调用 |
CURLSHE_INVALID | 句柄无效 | share 句柄被破坏或不是有效的CURLSH* |
CURLSHE_NOMEM | 内存不足 | 内部资源分配失败 |
CURLSHE_NOT_BUILT_IN | 特性未编译进库 | 库编译时禁用了对应功能(如无 SSL、无 Cookie) |
可使用 curl_share_strerror 将错误码转换为可读的错误描述字符串。
五、底层实现:specifier 标志位机制
理解 CURLSHOPT_UNSHARE 的底层原理,关键在于specifier位图字段。共享对象struct Curl_share用一个 32 位无符号整数specifier记录"当前共享了哪些数据",每种数据类型占一个位。
在 lib/curl_share.c 的CURLSHOPT_SHARE分支中:
case CURLSHOPT_SHARE: /* this is a type this share will share */ type = va_arg(param, int); /* ... 各类型初始化对应资源 ... */ if(!res) share->specifier |= (unsigned int)(1 << type); break;成功共享某类型数据后,通过share->specifier |= (1 << type)置位;而在CURLSHOPT_UNSHARE分支中(lib/curl_share.c),成功取消后通过share->specifier &= ~(unsigned int)(1 << type)清位。
这一位图在运行时被广泛用于共享行为的判定,例如 Curl_share_lock_share():
if(share->specifier & (unsigned int)(1 << type) && share->lockfunc) /* only call this if set! */ share->lockfunc(data, type, accesstype, share->clientdata); /* else if we do not share this, pretend successful lock */即:只有specifier中对应位被置位(该类型处于共享状态)时才调用应用层提供的加锁回调,否则直接返回成功。同理,Curl_share_unlock_share() 也只有在该类型仍处于共享状态时才调用解锁回调。
而 easy handle 与共享对象的绑定关系由Curl_share_easy_link()/Curl_share_easy_unlink()(lib/curl_share.c)管理:绑定时递增ref_count并让 easy handle 的字段(如data->cookies、data->hsts)指向共享资源;解绑时还原。这也解释了为什么"使用中"(ref_count > 1)时不能执行 UNSHARE——此时正在被引用的数据结构(如share->cookies)一旦被清理,已绑定的 easy handle 就会悬空。
在 curl_share_init() 创建共享对象时,specifier初始只置位内部使用的CURL_LOCK_DATA_SHARE位(share->specifier |= (1 << CURL_LOCK_DATA_SHARE)),其余数据类型的位均处于清零状态,即默认不共享任何数据——所有共享行为都需要应用层显式通过 CURLSHOPT_SHARE 开启,CURLSHOPT_UNSHARE 则用于反向关闭。
六、典型应用场景与注意事项
适用场景
- 动态调整共享策略:同一共享对象在程序生命周期内,前阶段共享 Cookie 用于会话保持,后阶段出于隔离需求取消共享,使各 easy handle 的 Cookie 互不干扰;
- 资源释放与回收:共享的 SSL 会话缓存、连接缓存占据内存,在不再需要时通过 UNSHARE 触发内部清理(Cookie 缓存会立即
Curl_cookie_cleanup()); - 与 CURLSHOPT_SHARE 配合实现局部共享:例如仅共享 DNS 与 SSL 会话(加速重连),但不共享 Cookie(保持隔离),通过先 SHARE 后按需 UNSHARE 灵活组合。
注意事项
- 必须传输间隙操作:UNSHARE 只能在没有任何 easy handle 使用该共享对象时执行,否则返回
CURLSHE_IN_USE; - 多线程场景:若共享数据被多线程访问,必须同时设置 CURLSHOPT_LOCKFUNC 与 CURLSHOPT_UNLOCKFUNC 回调;且 Cookie、连接、HSTS 等数据类型官方明确不支持跨并发线程共享;
- 编译期功能裁剪:未启用 TLS 的构建不支持 SSL 会话共享,禁用 Cookie/HTTP 的构建不支持 Cookie 共享,这些场景下 UNSHARE 会返回
CURLSHE_NOT_BUILT_IN; - UNSHARE 后无需显式恢复:取消共享后,easy handle 自然回归各自的独立数据维护逻辑,与从未共享过的行为一致;
- 共享对象生命周期:最终使用 curl_share_cleanup(3) 销毁共享对象时会统一释放所有仍存留的共享资源(lib/curl_share.c),因此 UNSHARE 更适合"运行中调整"而非"程序退出前的清理"。
七、相关文档与资源
- 反向操作:CURLSHOPT_SHARE(3) — 将数据类型加入共享
- 选项总览:curl_share_setopt(3) — 共享对象选项设置入口
- 生命周期:curl_share_init(3) 与 curl_share_cleanup(3)
- 错误处理:curl_share_strerror 与 libcurl-errors(3)
- 源码与测试:共享对象核心实现、CURL_LOCK_DATA 与 CURLSHOPT 枚举定义、共享功能综合测试 lib506、选项手册构建清单
【免费下载链接】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),仅供参考