尧图精选

libcurl WebSocket 完全指南:ws:// 握手升级、帧收发 API 与底层实现解析

🕒 发布时间:2026/9/10 13:28:56 📁 来源:尧图网络
libcurl WebSocket 完全指南ws:// 握手升级、帧收发 API 与底层实现解析【免费下载链接】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/curlWebSocket 是一种基于 HTTP 的、面向消息的双向通信协议RFC 6455curl 通过ws://与wss://两种 URL scheme 对其提供完整支持。本文以仓库内部设计文档 docs/internals/WEBSOCKET.md 为主线结合 lib/ws.c、include/curl/websockets.h 及配套手册源码系统讲解 libcurl 的两种 WebSocket 编程模型、帧收发 API、原始模式与自动 PONG 等选项、64K 帧大小限制、错误语义以及测试套件与未来规划帮助你直接用 curl 库构建实时双向通信应用。WebSocket 在 curl 中的定位与设计思路WebSocket 通常也被写作 WebSockets复数本质上是把一次普通 HTTP(S) GET 请求“升级”Upgrade为一条长期存活、可双向同时发送消息的连接协议规范为 RFC 6455。与 TCP 的字节流模型不同WebSocket 是面向消息的两端收发的是完整消息消息在线路上由一个或多个帧frame承载。curl 的实现原则是“自研协议栈”设计文档明确给出不依赖第三方 libWebSocket 库的理由包括 doxygen 文档可读性差、与特定 TLS 库耦合过紧、与事件库绑定、线程模型过重、代码量巨大比 libcurl 本身还大等而 WebSocket 在网络/帧层是一个相当简单的协议自研完全可控。这条主线贯穿了后续的 API 形态、测试策略与扩展边界。从源码结构看WebSocket 支持以编译开关CURL_DISABLE_WEBSOCKETS和CURL_DISABLE_HTTP控制见 lib/ws.h关闭时相关函数退化为空操作便于裁剪到最小构建。URL 与握手升级机制WebSocket 通信通过ws://或wss://URL 建立ws://明文 WebSocket跑在 HTTP 之上wss://安全 WebSocket跑在 HTTPS 之上。使用wss://时标准 TLS/HTTPS 选项全部生效——CA 证书路径CURLOPT_CAINFO、证书校验CURLOPT_SSL_VERIFYPEER/CURLOPT_SSL_VERIFYHOST等行为与普通 HTTPS 请求完全一致不需要为 WebSocket 单独引入 TLS 逻辑。握手的底层过程是libcurl 发起一次 HTTP/1 GET 请求携带Upgrade: websocket、Connection: Upgrade、Sec-WebSocket-Version: 13以及随机生成的Sec-WebSocket-Key头服务器同意后以101 Switching Protocols响应此后连接进入 WebSocket 数据帧阶段。这一点可由测试用例 tests/data/test2300 中记录的客户端协议数据印证GET /%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ Connection: Upgrade升级失败即错误当给定 WebSocket URL 时libcurl 把“升级成功”视为该传输的前提条件。如果服务器没有以 101 响应而是返回了普通 HTTP 状态码哪怕是 2xx这次传输都会以失败告终——详见下文“错误处理”一节。两种 API 编程模型WebSocket 的用途远比“纯下载/纯上传”灵活因此 libcurl 提供了两套不同的编程模型二者通过CURLOPT_CONNECT_ONLY的取值切换详见 docs/libcurl/libcurl-ws.md模型一Write/Read 回调模型下载/上传导向要求CURLOPT_CONNECT_ONLY不设置或为0L。此时curl_easy_perform()完成握手后会阻塞整个连接存续期间接收每当有 WebSocket 数据块到达libcurl 调用CURLOPT_WRITEFUNCTION指定的写回调把帧负载交给应用应用可在回调内或回调外调用curl_ws_send()回复数据发送libcurl 8.16.0注册CURLOPT_READFUNCTION读回调并同时设置CURLOPT_UPLOAD连接建立后回调被反复调用以取得待发送数据。默认情况下读回调返回的数据被封装为CURLWS_BINARY帧发出如需发送其他帧类型或更长的帧可在读回调中调用curl_ws_start_frame()显式开启一个新帧暂停/恢复使用curl_multi_perform()驱动时读回调可返回CURL_READFUNC_PAUSE表示暂无数据之后调用curl_easy_pause()恢复上传读回调会再次被调用。curl_ws_meta()专为写回调设计它只能在收到 WebSocket 数据的写回调内调用返回当前帧的元数据。由于 libcurl 不会把 easy handle 指针传给回调应用需要自行通过CURLOPT_WRITEDATA把句柄传入回调才能在回调里调用curl_ws_meta()。模型二CONNECT_ONLY 模型主动收发导向CURLOPT_CONNECT_ONLY设置为2L这是 WebSocket 引入的新取值普通连接只用 1L。此时curl_easy_perform()只负责完成“HTTP GET Upgrade 请求/响应”的握手阶段随后立即把控制权交还给应用。应用再通过两个专用函数进行帧级收发curl_ws_recv()接收一个 WebSocket 帧curl_ws_send()发送一个 WebSocket 帧。这个模型最适合实现自定义 WebSocket 客户端协议逻辑例如聊天客户端、实时推送订阅等场景。函数原型见 include/curl/websockets.hCURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *recv, const struct curl_ws_frame **metap); CURLcode curl_ws_send(CURL *curl, const void *buffer, size_t buflen, size_t *sent, curl_off_t fragsize, unsigned int flags);两者都不阻塞当需要等待 socket 可读/可写时会返回CURLE_AGAIN正确的做法是用select()/poll()等待 socket 事件后再调用。curl_ws_recv()在连接关闭时返回CURLE_GOT_NOTHING。发送时若返回CURLE_OK但*sent小于buflen说明一次调用未能消费完整个负载必须用更新后的 buffer/长度参数继续调用直至全部发送完成。CURLOPT_WS_OPTIONS行为控制选项CURLOPT_WS_OPTIONS选项号 320见 lib/setopt.c接受一个 long 型位掩码目前定义两个位完整手册见 docs/libcurl/opts/CURLOPT_WS_OPTIONS.md位标志值说明CURLWS_RAW_MODE1L 0原始模式把网络上未经解析的 WebSocket 流量原样交给写/读回调帧解析完全由应用负责CURLWS_NOAUTOPONG1L 1禁用自动 PONG 回复libcurl 8.14.0 新增此时应用必须用curl_ws_send()主动回 PONG默认值为0即关闭原始模式、开启自动 PONG。在 lib/setopt.c 中可以看到该选项被解析为两个独立的状态位case CURLOPT_WS_OPTIONS: s-ws_raw_mode (bool)(arg CURLWS_RAW_MODE); s-ws_no_auto_pong (bool)(arg CURLWS_NOAUTOPONG); break;注意CURLWS_RAW_MODE与CURLWS_NOAUTOPONG宏在 8.16.0 之前是 int 类型传给curl_easy_setopt()时需要 long 强转8.16.0 起二者本身即为 long 类型。原始模式RAW MODE的适用场景原始模式意味着 libcurl 不再解析任何帧也不替应用处理 PING 等控制帧——所有网络字节原样进出回调。它的典型用途是应用已具备自研的 WebSocket 解析/引擎只想借 libcurl 完成 TLS 握手与升级从而保留既有软件架构。例如CURL *curl curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, ws://example.com/); /* 告诉 curlWebSocket 逻辑全部由我们自己处理 */ curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, CURLWS_RAW_MODE); CURLcode result curl_easy_perform(curl); curl_easy_cleanup(curl);自动 PONG 与心跳WebSocket 面向长连接设计为保持连接存活任一端都可以发送 PING另一端应以回显负载的 PONG 应答两端也可以发送主动 PONG 作为单向心跳。libcurl 的默认行为是收到服务器的 PING 后自动回送一个回显负载的 PONG但 libcurl 自身不会主动发送任何 PING 或主动 PONG。自动应答可通过CURLOPT_WS_OPTIONS关闭CURLWS_NOAUTOPONG关闭后由应用在合适时机调用curl_ws_send()并附带CURLWS_PONG标志手动应答。帧结构、标志位与 curl_ws_meta 元数据WebSocket 帧的线格式定义在 RFC 6455 第 5.2 节lib/ws.c 中直接以位操作实现了该头部解析#define WSBIT_FIN 0x80 #define WSBIT_RSV1 0x40 #define WSBIT_RSV2 0x20 #define WSBIT_RSV3 0x10 #define WSBIT_OPCODE_CONT 0x0 /* 延续帧 */ #define WSBIT_OPCODE_TEXT 0x1 /* 文本帧 */ #define WSBIT_OPCODE_BIN 0x2 /* 二进制帧 */ #define WSBIT_OPCODE_CLOSE 0x8 /* 关闭帧 */ #define WSBIT_OPCODE_PING 0x9 /* 心跳帧 */ #define WSBIT_OPCODE_PONG 0xa /* 心跳应答帧 */ #define WSBIT_MASK 0x80 /* 客户端帧必须掩码 */其中 FIN 位标识帧是否为一个分片消息的最后一帧控制帧CLOSE/PING/PONG绝不允许分片且负载上限为 125 字节源码中WS_MAX_CNTRL_LEN 125与此对应。libcurl 通过 include/curl/websockets.h 中的struct curl_ws_frame向应用描述帧状态struct curl_ws_frame { int age; /* 恒为 0保留字段 */ int flags; /* 见下方 CURLWS_* 位定义 */ curl_off_t offset; /* 本数据块在完整帧负载中的偏移 */ curl_off_t bytesleft; /* 该帧剩余未交付的字节数 */ size_t len; /* 当前数据块的长度 */ };帧/消息标志位含义互斥的消息类型标志CURLWS_TEXT/BINARY/CLOSE/PING/PONG一次只置一个标志位含义CURLWS_TEXT10文本消息。libcurl 不校验内容不保证是合法 UTF-8CURLWS_BINARY11二进制消息CURLWS_CONT12分片消息中的延续帧除最后一帧外均置位CURLWS_CLOSE13关闭消息之后无更多数据。负载可含 2 字节网络字节序的关闭码外加最多 123 字节文本合计 ≤125 字节CURLWS_PING14PING负载最多 125 字节CURLWS_OFFSET15发送方向本次数据只是大帧的一部分CURLWS_PONG16仅curl_ws_send()使用发送 PONG消息可能被拆成多个帧帧又可能因过大被拆成多个 chunk 交付给应用因此bytesleft是接收方最需要关注的值若bytesleft 0说明当前帧尚未收完需继续调用curl_ws_recv()或等待下一次写回调。完整字段与标志说明见 docs/libcurl/curl_ws_meta.md。发送大帧curl_ws_send 与 curl_ws_start_framecurl_ws_send()的flags参数必须至少包含一个消息类型标志。发送分片消息时除最后一帧外的所有帧都应附加CURLWS_CONT位且每一帧都应带消息类型位仅为向后兼容才允许延续帧省略类型位官方强烈不鼓励。发送超长帧则使用CURLWS_OFFSET机制第一次调用时置CURLWS_OFFSET位并把fragsize设为该帧的总长度后续各次调用fragsize传 0。一个完整的发送示例摘自 docs/libcurl/curl_ws_send.mdstatic const char *buffer PAYLOAD; size_t offset 0; CURLcode result CURLE_OK; CURL *curl curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, wss://example.com/); curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); curl_easy_perform(curl); /* 建立 HTTPS 连接并升级到 WSS然后返回控制权 */ while(!result) { size_t sent; result curl_ws_send(curl, buffer offset, strlen(buffer) - offset, sent, 0, CURLWS_TEXT); offset sent; if(result CURLE_OK offset strlen(buffer)) break; /* 发送完毕 */ if(result CURLE_AGAIN) result CURLE_OK; /* 真实应用中应在此等待 socket 可写 */ } curl_easy_cleanup(curl);curl_ws_start_frame()libcurl 8.16.0 新增见 docs/libcurl/curl_ws_start_frame.md用于在CURLOPT_READFUNCTION回调中显式开启一个指定类型、指定长度的新帧随后回调返回的数据即属于该帧若前一帧尚未被完全读取调用会失败在CURLWS_RAW_MODE下调用会失败关键行为差异libcurl 通常把读回调返回 0 当作文件结束EOF而停止读取但只要读回调调用过curl_ws_start_frame()返回 0 就不再视为 EOFlibcurl 会继续调用读回调——这使得发送长度为零的 WebSocket 帧成为可能。帧大小上限当前 64K 限制与原因设计文档明确声明当前实现仅支持最大约 64K 的帧大小。这一限制的根源在于API 按完整帧交付curl_ws_recv()与写回调都要求把整个帧装入内存缓冲而帧头部理论上可声明高达 2^63 字节的长度两者无法匹配。从 lib/ws.c 的缓冲区常量可以印证这一量级#define WS_CHUNK_SIZE 65535 #define WS_CHUNK_COUNT 2接收/发送缓冲按 65535 字节的 chunk 组织。若未来需要支持远超 64K 的帧官方路线是调整 API使其在收发两个方向都能交付部分帧partial frames。对应用而言当前应把单帧负载控制在 64K 以内跨帧的长消息则通过分片fragmentation或CURLWS_OFFSET机制实现。错误处理语义WebSocket 传输的错误语义与普通 HTTP 传输有显著区别若ws:///wss://URL 未能通过 101 响应完成升级而是从 HTTP 服务器收到其他状态码该传输返回CURLE_HTTP_RETURNED_ERROR注意即使是 2xx 状态码也按错误处理——因为它没有提供 WebSocket 传输。也就是说“连接成功”与“升级成功”在 WebSocket 场景下是两个不同的判定标准应用不能仅凭 HTTP 层面成功就认为 WebSocket 已就绪。curl_ws_recv()额外返回CURLE_GOT_NOTHING表示关联连接已关闭curl_ws_send()/curl_ws_recv()在需要等待 socket 时返回CURLE_AGAIN应用应等待 socket 事件后重试而不是忙轮询。测试套件扩展 sws 服务器做协议级验证设计文档记录了一个重要的测试决策为了在测试套件中验证 WebSocket作者没有引入现成的小型 WebSocket 服务器而是扩展既有测试服务器 swstests/server/sws.c一个“简单甚至有点笨”的测试 HTTP 服务器。理由有二sws 已深度集成在测试套件中开箱即用测试需要最大自由度地去构造坏协议与负向用例——一个更“笨”、更简单的 TCP 服务器比一个“正经”的 WebSocket 服务器更容易被揉捏成各种畸形场景。sws 中确实增加了upgrade相关的服务器指令支持见 tests/server/sws.c 中bool upgrade字段与sws_parse_servercmd()。对应的测试用例从 test2300 起成规模存在如 tests/data/test2300 用-T . ws://...验证“仅升级”场景features中声明ws特性响应端返回HTTP/1.1 101 Switching Protocols并回放Sec-WebSocket-Accept。这类测试既覆盖了正常握手也为后续加入畸形帧、坏掩码、非法分片等负向用例预留了空间。命令行工具telnet/nc 风格的 WebSocket 会话规划中设计文档明确指出命令行 curl 工具做 WebSocket 的计划是让它像telnet/nc一样进行交互式会话而该部分工作在文档撰写时尚未开始。规划中的能力包括从 stdin 读取并作为消息发送把换行视为片段fragment结束默认文本提供选项切换二进制自动应答 PING按默认间隔主动发 PING提供关闭/改间隔选项允许-d指定初始发送数据并考虑格式是否支持一次发送多个独立帧在收到 N 条消息后退出N 可为 0。与此同时测试套件中已出现命令行 curl 完成ws://升级的用例见上文 test2300说明工具侧已具备发起 WebSocket 传输的基础能力只是交互式会话尚未落地。未来工作与已知边界设计文档列出的后续工作包括校验响应中的Sec-WebSocket-Accept需要引入 sha-1 计算校验响应中的Sec-WebSocket-Extensions与Sec-WebSocket-Protocol考虑提供curl_ws_poll()保证 WebSocket 代码路径纳入 fuzz 测试增加客户端主动 PING 的间隔配置提供禁用 PING/PONG 自动化的开关支持压缩扩展CURLWS_COMPRESS目前未实现。其中“禁用自动 PONG”已在 8.14.0 落地为CURLWS_NOAUTOPONG。此外libcurl 目前不支持任何 WebSocket 扩展协商建立连接时总是请求无扩展连接见 docs/libcurl/libcurl-ws.md。控制帧PING/PONG/CLOSE可以插入到任意两个用户数据帧之间——甚至插在同一个分片消息的两个片段中间应用在重组分片消息时须正确处理这一点。总结curl 的 WebSocket 支持呈现出清晰的“自研、可控、渐进”特征ws:///wss://URL 复用既有的 HTTP/TLS 基础设施完成升级两种 API 模型分别服务“回调驱动的持续传输”与“主动帧级收发”两类场景CURLWS_RAW_MODE允许已有 WebSocket 引擎的应用只借用 curl 的握手与传输而 64K 帧上限、错误语义与测试策略等设计取舍都能在 docs/internals/WEBSOCKET.md 与 lib/ws.c 中得到互相印证的依据。无论你是想实现一个实时推送客户端、协议网关还是为已有应用嫁接 WebSocket 能力上述 API 与限制都构成了完整的落地路线图。【免费下载链接】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),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →