
libcurl WebSocket 数据接收实战curl_ws_recv 全面解析【免费下载链接】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_ws_recv是 libcurl WebSocket 接口libcurl-ws中基于CURLOPT_CONNECT_ONLY模型的核心接收函数用于从已建立的 WebSocket 连接上按帧、按分片读取服务端数据。本文以 curl_ws_recv 官方手册 为主体结合仓库中 lib/ws.c 的实现与 tests/libtest/lib2700.c、docs/examples/websocket.c 等测试与示例系统讲解该函数的签名、参数语义、帧元数据结构、分片消息重组、非阻塞语义及完整可运行代码帮助你在自己的应用中正确、健壮地接收 WebSocket 消息。函数签名与整体定位curl_ws_recv声明于公开头文件 include/curl/websockets.h通过#include curl/curl.h即可获得CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *recv, const struct curl_ws_frame **meta);它在 7.86.0 版本加入见文档 front-matter 的Added-in: 7.86.0属于CURLOPT_CONNECT_ONLY模型应用程序先以ws://或wss://URL 建立 easy handle设置CURLOPT_CONNECT_ONLY为2L并调用curl_easy_perform完成 HTTP Upgrade 握手服务端返回 101 Switching Protocols随后通过curl_ws_recv/curl_ws_send与服务器双向通信。这与回调模型CURLOPT_WRITEFUNCTIONcurl_ws_meta互补适合自行控制收发节奏、需要拿到控制权的客户端场景。参数详解与核心语义curl、buffer、buflen、recvcurl目标 easy handle必须指向一条已完成 WebSocket 升级的传输。若传入的句柄没有连接或未启用CONNECT_ONLY从 lib/ws.c 的实现可见会返回CURLE_BAD_FUNCTION_ARGUMENT或CURLE_UNSUPPORTED_PROTOCOL并输出 [WS] CONNECT_ONLY is required 等错误信息。buffer/buflen接收缓冲区及其容量。函数会尽可能多地把当前帧载荷拷入缓冲区但最多不超过 buflen 字节。recv输出参数返回实际写入缓冲区的字节数调用时初始化为 0若出错则为 0。一个容易踩坑的边界当buflen非零而buffer为 NULL 时实现会直接返回CURLE_BAD_FUNCTION_ARGUMENT因此缓冲区指针与长度必须配套有效。meta帧元数据指针调用成功后meta被设置为指向一个const struct curl_ws_frame描述本次返回数据所属的帧。该结构体定义在 include/curl/websockets.hstruct curl_ws_frame { int age; /* zero */ int flags; /* See the CURLWS_* defines */ curl_off_t offset; /* the offset of this data into the frame */ curl_off_t bytesleft; /* number of pending bytes left of the payload */ size_t len; /* size of the current data chunk */ };字段语义与 curl_ws_meta 中的说明一致age结构体版本当前恒为 0。flags位掩码描述帧类型取值见下文 FLAGS 一节。offset本次数据块在整个帧载荷中的起始偏移。多帧/分片场景下用于定位。bytesleft当前帧尚未交付的剩余字节数是判断帧是否接收完整的关键字段。len本次实际交付的数据块长度通常等于*recv。生命周期约束该结构体由 libcurl 内部持有应用不得释放一旦再次调用任何 WebSocket 函数curl_ws_recv、curl_ws_send、curl_ws_meta等其内容即失效不应再被引用。实现上该元数据由 lib/ws.c 的update_meta()在每次返回前更新到ws-recvframe并回传指针。通过 bytesleft 判断帧是否完整手册明确要求应用必须检查meta-bytesleft。它大于 0 表示当前帧还有载荷未交付原因可能是提供的缓冲区不够大一次装不下整帧网络上该帧的数据尚未全部到达。此时应使用更新后的buffer offset与剩余空间再次调用curl_ws_recv继续接收直到bytesleft 0。官方示例中的循环骨架如下完整代码见 curl_ws_recv.mdwhile(!result) { size_t recv; const struct curl_ws_frame *meta; result curl_ws_recv(curl, buffer offset, sizeof(buffer) - offset, recv, meta); offset recv; if(result CURLE_OK) { if(meta-bytesleft 0) break; /* finished receiving */ if(meta-bytesleft (curl_off_t)(sizeof(buffer) - offset)) result CURLE_TOO_LARGE; } if(result CURLE_AGAIN) /* in real application: wait for socket here, e.g. using select() */ result CURLE_OK; }测试 tests/libtest/lib2700.c 也印证了这一模式先调用recv_header取得flags/offset/bytesleft然后while(bytesleft 0)循环recv_chunk并用DEBUGASSERT(meta-bytesleft (*bytesleft - nread))校验每次返回后bytesleft的递减关系——即每次调用消耗的字节数等于本次*recv剩余量等于上一轮剩余减去本次接收量。零长度调用只读元数据、不消费载荷若只想查看下一帧的元数据而不想消费任何载荷可以传buflen 0此时buffer允许为 NULL。需要注意无载荷的帧如空 PING/PONG/CLOSE会被这次调用消费掉。这一特性对先侦查再决定是否接收的控制流很有用测试 tests/libtest/lib2700.c 中即以curl_ws_recv(curl, NULL, 0, nread, meta)形式探测帧头信息。帧类型标志FLAGSmeta-flags的取值定义在 include/curl/websockets.h含义与 curl_ws_meta 文档一致标志值含义CURLWS_TEXT10文本消息帧libcurl 不校验内容是否为合法 UTF-8CURLWS_BINARY11二进制消息帧CURLWS_CONT12分片消息的续帧非最后一片只能与 TEXT/BINARY 同时出现CURLWS_CLOSE13关闭帧之后不再有数据CURLWS_PING14Ping 帧载荷 ≤ 125 字节libcurl 默认自动回 PongCURLWS_PONG15Pong 帧载荷 ≤ 125 字节消息类型标志TEXT/BINARY/CLOSE/PING/PONG互斥。CURLWS_CONT仅能伴随 TEXT 或 BINARY 出现表示本帧不是消息的最后一片同一条消息还有后续帧。分片消息与控制帧的处理WebSocket 协议中一条消息message在网络上可能拆成多帧frame传输即分片fragmented消息。libcurl 在接收时以块chunk为单位交付数据一块可能是一整帧也可能是帧的一部分取决于缓冲区大小与网络到达情况。手册对分片场景给出三条关键指引CURLWS_CONT置位除最后一帧外分片消息的每个帧都带有CURLWS_CONT位而CURLWS_TEXT/CURLWS_BINARY类型位在每一个分片首个、中间、末尾上都存在。因此判断消息是否收完不能只看类型位必须跟踪最后一个不带CURLWS_CONT的帧。应用自行重组分片消息的拼接是应用的责任libcurl 不提供自动重组。控制帧可插入分片之间CLOSE、PING、PONG 这类控制帧允许出现在分片消息的任意两帧之间协议上控制帧不得再被分片且最大 125 字节。应用在重组时务必正确处理这种交错到达否则会把控制帧误当消息内容。关于控制帧还有一点实现细节值得注意libcurl 在收到 PING 时会自动回复等载荷的 PONG除非在CURLOPT_WS_OPTIONS中设置CURLWS_NOAUTOPONG或CURLWS_RAW_MODE见 lib/ws.c 的ws_client_collect()当帧为 PING 且无剩余载荷时直接以ws_enc_add_cntrl(..., CURLWS_PONG)构造 PONG 并回写因此这类 PING 帧不会以用户数据形式返回给应用。非阻塞语义与返回值curl_ws_recv是非阻塞的CURLE_OK0成功*recv为本次收到的字节数*meta指向有效元数据。CURLE_AGAIN当前没有数据可读函数直接返回而不阻塞。正确做法是等待 socket 变为可读如select()/poll()/epoll后再调用。CURLE_GOT_NOTHING底层连接已关闭手册在 RETURN VALUE 一节明确Returns CURLE_GOT_NOTHING if the associated connection is closed实现见 lib/ws.c当从网络层读到 0 字节时返回该错误码。其他非零值各类错误可在设置CURLOPT_ERRORBUFFER后从错误缓冲区取得描述完整列表见 libcurl-errors 手册。从实现看lib/ws.c 的curl_ws_recv内部是一个循环先检查接收缓冲区ws-recvbuf是否为空为空则用Curl_bufq_slurp从网络灌入然后经ws_dec_pass解码 WebSocket 帧并交给ws_client_collect收集若解码器返回CURLE_AGAIN且尚未写出任何字节need more input则继续读网络一旦成功写出数据即返回给调用者。这种有数据就返回、没数据不等待的设计正是非阻塞语义的底层来源。完整示例一带分片检查的循环接收下面是在手册示例基础上补充了分片处理与错误打印的完整版本框架与 docs/examples/websocket.c 中的recv_pong一脉相承#include curl/curl.h #include stdio.h #include string.h int main(void) { char buffer[1024]; 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); /* start HTTPS connection and upgrade to WSS, then return control */ curl_easy_perform(curl); while(!result) { size_t recv; const struct curl_ws_frame *meta; result curl_ws_recv(curl, buffer offset, sizeof(buffer) - offset, recv, meta); offset recv; if(result CURLE_OK) { /* print one chunk */ fprintf(stderr, frame flags0x%x offset%lld len%zu bytesleft%lld\n, (unsigned int)meta-flags, (long long)meta-offset, meta-len, (long long)meta-bytesleft); if(meta-flags CURLWS_CLOSE) { /* server closed the connection */ result CURLE_OK; break; } if(meta-bytesleft 0) { /* complete frame received: reassemble across frames using CURLWS_CONT tracking in a real application */ break; } if(meta-bytesleft (curl_off_t)(sizeof(buffer) - offset)) result CURLE_TOO_LARGE; /* buffer too small for this frame */ } else if(result CURLE_AGAIN) { /* wait for socket readability, e.g. select()/poll() */ result CURLE_OK; } /* CURLE_GOT_NOTHING: connection closed - loop exits */ } curl_easy_cleanup(curl); return (int)result; }要点回顾offset累计已接收字节每次以buffer offset、sizeof(buffer) - offset续传用bytesleft判断帧是否收完CURLE_AGAIN表示需等待可读事件CURLE_TOO_LARGE表示帧大于缓冲区、需要更大的 buffer或改为按块消费。完整示例二Ping/Pong 交互与关闭仓库示例仓库自带的可运行示例 docs/examples/websocket.c 演示了完整的客户端交互先发 PINGcurl_ws_send(..., CURLWS_PING)再循环curl_ws_recv等待 PONG依据meta-flags区分 PONG/TEXT/BINARY 帧最后以零载荷的CURLWS_CLOSE帧关闭连接static CURLcode recv_pong(CURL *curl, const char *expected_payload) { size_t rlen 0; const struct curl_ws_frame *meta; char buffer[256]; CURLcode result; retry: result curl_ws_recv(curl, buffer, sizeof(buffer), rlen, meta); if(result CURLE_OK) { if(meta-flags CURLWS_PONG) { /* compare payload with expected */ ... } else if(meta-flags CURLWS_TEXT) { /* handle text frame */ ... } else if(meta-flags CURLWS_BINARY) { /* handle binary frame */ ... } else { /* some other frame, e.g. CLOSE: retry for next */ goto retry; } } else if(result CURLE_AGAIN) { /* blocked, wait and retry */ sleep(1); goto retry; } return result; }该示例同时示范了CURLE_AGAIN的典型处理不忙等而是 sleep 或select()等待后再重试示例作者也注明大帧会分块到达Larger frames arrive in chunks与本文分片讨论相互印证。结合源码的实现要点速查参数校验buflen !buffer返回CURLE_BAD_FUNCTION_ARGUMENTlib/ws.c未启用CONNECT_ONLY返回CURLE_UNSUPPORTED_PROTOCOL找不到 WebSocket 连接元数据返回CURLE_BAD_FUNCTION_ARGUMENTlib/ws.c。元数据生成update_meta()计算bytesleft payload_len - payload_offset - cur_len并写入ws-recvframelib/ws.c因此bytesleft语义为整帧剩余未交付字节与手册表述完全一致。自动 PONGws_client_collect()中auto_pong由data-set.ws_no_auto_pong决定lib/ws.c对应CURLWS_NOAUTOPONG选项。测试佐证tests/libtest/lib2700.c 用curl_ws_recv(curl, NULL, 0, ...)探测帧头、并用bytesleft递减断言验证分片接收逻辑tests/libtest/cli_ws_data.c 与 tests/data/test2700 等数据文件支撑相关测试用例可作为深入理解行为的参考。常见错误与注意事项忘记检查bytesleft这是最常见的 bug。即使一次curl_ws_recv返回成功也不代表整帧收完必须循环直到bytesleft 0。复用失效的meta调用任何其他 WebSocket 函数后旧meta指针不可再用如需保留应自行拷贝字段。忽略分片CURLWS_CONT消息跨多帧时需自行重组并用CURLWS_TEXT/CURLWS_BINARY而非 CONT判断最终语义同时留意穿插其间的 CLOSE/PING/PONG。阻塞式等待CURLE_AGAIN出现时应等待 socket 可读再重试而不是空转或把返回值当错误。缓冲区过小帧可能大于缓冲区处理方式为分块消费或增大缓冲区示例中bytesleft 剩余空间时返回CURLE_TOO_LARGE。连接关闭判定CURLE_GOT_NOTHING表示连接已关闭应据此结束接收循环并做清理。延伸阅读curl_ws_send配套的发送函数含CURLWS_OFFSET大帧分片发送curl_ws_metastruct curl_ws_frame与全部标志位的权威说明libcurl-wsWebSocket 接口总览、两种使用模型与 RAW MODECURLOPT_WS_OPTIONSCURLWS_RAW_MODE/CURLWS_NOAUTOPONG选项lib/ws.cWebSocket 编解码与收发核心实现docs/examples/websocket.c可编译运行的完整示例tests/libtest/lib2700.c接收流程与分片逻辑的测试佐证【免费下载链接】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),仅供参考