☰
libwebsockets 原始套接字收养(RAW Socket Adoption)实战:把外部 TCP 连接交给 lws 事件循环
2026/10/14 3:02:19 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

导读

本文以 libwebsockets 仓库中的 minimal-raw-adopt-tcp 示例为蓝本,讲解 libwebsockets 最实用的集成能力之一:Raw Socket Adoption(套接字收养)。当你的应用已经通过自己的代码建立了一个已连接的 TCP 套接字(例如使用socket()/connect()自行完成的连接,或从第三方库手中获得的 fd),你不需要把它丢给另一套线程模型,而是可以直接让 libwebsockets "收养"(adopt)这个套接字,将其包装成一个 raw wsi,纳入 lws 自身的服务循环统一管理。读完本文你将掌握:何时应该使用lws_adopt_descriptor_vhost()、raw 协议回调(LWS_CALLBACK_RAW_ADOPT/RAW_RX/RAW_WRITEABLE/RAW_CLOSE)如何协同工作、示例程序从编译到运行的全过程,以及这一能力在源码层面的实现原理。

一、为什么需要"收养"外部套接字

1.1 适用场景

libwebsockets 是自带事件循环(event loop)的网络库,lws_service()负责驱动所有 wsi 的 IO。正常情况下,一个连接应该由 lws 自己创建:客户端连接使用lws_client_connect_via_info(),服务端连接由 listen 套接字accept()产生。但现实集成中常常遇到另一种情况——你的应用程序已经自行建立并连接好了 socket,例如:

  • 主程序用原生 POSIX API(socket+getaddrinfo+connect)或第三方网络库建立连接;
  • 上层业务逻辑在建立连接前后做了自定义处理(如特定握手、代理协商);
  • 你希望保留这些已连接 socket 的同时,把它们交给 lws 的 raw 角色来收发数据。

示例 README 明确指出了这个场景的定位:

This example is only meaningful if you are integrating lws in another app which generates its own connected sockets. In some cases you may want lws to "adopt" the socket.

"收养"的意义在于:所有权移交。lws_adopt_descriptor_vhost()成功返回后,这个 fd 的读写、关闭、可写通知全部由 lws 事件循环接管,你不再需要自己维护 fd 的 epoll/poll 注册,只需要在 raw 回调里处理业务数据即可。

1.2 什么时候不需要收养

如果你只是想用 lws 单独创建一个 raw 客户端连接,那么完全不需要绕这一圈——直接用lws_client_connect_via_info()并把info.method设置为"RAW"即可,lws 会走它自己完整的 dns 解析、连接、建立流程。README 中特别注明可以参考 http-client/minimal-http-client 的写法:把info.method改成"RAW"就是同样的效果。

用一句话区分:

  • 让 lws 自己建连→lws_client_connect_via_info()+info.method = "RAW";
  • 接管别人已建好的连接→lws_adopt_descriptor_vhost()系列 API。

二、示例程序整体设计

minimal-raw-adopt-tcp演示的流程非常直观(完整源码见 minimal-raw-adopt-tcp.c):

  1. 创建 lws context 和一个不监听任何端口的 vhost(CONTEXT_PORT_NO_LISTEN_SERVER),并注册一个名为raw-test的协议;
  2. 用原生getaddrinfo()+socket()+connect()自行连接到libwebsockets.org:80——这一步完全不经过 lws,模拟"外部程序已经建好连接"的现实场景;
  3. 调用lws_adopt_descriptor_vhost()把这个"外来" socket 收养为 raw wsi,绑定到raw-test协议;
  4. lws 接管后,在回调里向远端写入"GET / HTTP/1.1\r\n\r\n",并把收到的响应做 hexdump 打印;
  5. 由于这是一个不懂 HTTP 语义的 raw 套接字,连接不会被 lws 主动关闭,直到服务端 5 秒空闲超时断开,此后程序继续运行直到你按^C。

该示例在 raw 系列示例中的定位,见 minimal-examples-lowlevel/raw/README.md 中的一览表:minimal-raw-adopt-tcp即 "Shows how to have lws adopt an existing tcp socket something else had connected"。

三、构建与运行

3.1 编译

示例采用 CMake 构建,依赖已安装的 libwebsockets 包。进入示例目录后执行:

$ cmake . && make

构建配置(CMakeLists.txt)中通过require_lws_config(LWS_WITH_SERVER 1 requirements)检查当前 libwebsockets 是否开启了LWS_WITH_SERVER编译选项——因为收养 raw socket 属于服务端/原生集成能力,若未开启该选项则跳过构建。链接时使用websockets_shared(共享库)或websockets(静态库),取决于你的安装方式。

3.2 运行与输出

直接运行:

$ ./lws-minimal-raw-adopt-tcp

示例 README 给出了真实的运行日志(时间戳为示例编写年代):

[2018/03/23 09:03:57:1960] USER: LWS minimal raw adopt tcp [2018/03/23 09:03:57:1961] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off [2018/03/23 09:03:57:2079] USER: Starting connect... [2018/03/23 09:03:57:4963] USER: Connected... [2018/03/23 09:03:57:4963] USER: LWS_CALLBACK_RAW_ADOPT [2018/03/23 09:03:57:7842] USER: LWS_CALLBACK_RAW_RX (186) [2018/03/23 09:03:57:7842] NOTICE: [2018/03/23 09:03:57:7842] NOTICE: 0000: 48 54 54 50 2F 31 2E 31 20 33 30 31 20 52 65 64 HTTP/1.1 301 Red [2018/03/23 09:03:57:7842] NOTICE: 0010: 69 72 65 63 74 0D 0A 73 65 72 76 65 72 3A 20 6C irect..server: l [2018/03/23 09:03:57:7842] NOTICE: 0020: 77 73 77 73 0D 0A 53 74 72 69 63 74 2D 54 72 61 wsws..Strict-Tra [2018/03/23 09:03:57:7843] NOTICE: 0030: 6E 73 70 6F 72 74 2D 53 65 63 75 72 69 74 79 3A nsport-Security: [2018/03/23 09:03:57:7843] NOTICE: 0040: 20 6D 61 78 2D 61 67 65 3D 31 35 37 36 38 30 30 max-age=1576800 [2018/03/23 09:03:57:7843] NOTICE: 0050: 30 20 3B 20 69 6E 63 6C 75 64 65 53 75 62 44 6F 0 ; includeSubDo [2018/03/23 09:03:57:7843] NOTICE: 0060: 6D 61 69 6E 73 0D 0A 6C 6F 63 61 74 69 6F 6E 3A mains..location: [2018/03/23 09:03:57:7843] NOTICE: 0070: 20 68 74 74 70 73 3A 2F 2F 6C 69 62 77 65 62 73 https://libwebs [2018/03/23 09:03:57:7843] NOTICE: 0080: 6F 63 6B 65 74 73 2E 6F 72 67 0D 0A 63 6F 6E 74 ockets.org..cont [2018/03/23 09:03:57:7843] NOTICE: 0090: 65 6E 74 2D 74 79 70 65 3A 20 74 65 78 74 2F 68 ent-type: text/h [2018/03/23 09:03:57:7843] NOTICE: 00A0: 74 6D 6C 0D 0A 63 6F 6E 74 65 6E 74 2D 6C 65 6E tml..content-len [2018/03/23 09:03:57:7843] NOTICE: 00B0: 67 74 68 3A 20 30 0D 0A 0D 0A gth: 0.... [2018/03/23 09:03:57:7843] NOTICE: [2018/03/23 09:04:03:3627] USER: LWS_CALLBACK_RAW_CLOSE

日志揭示的完整链路非常清晰:

  • LWS minimal raw adopt tcp:程序启动;
  • Creating Vhost 'default' port 7681:创建了不监听的 vhost(7681 只是默认的展示端口号,实际没有 listen socket);
  • Starting connect.../Connected...:示例自己用 POSIX API 完成了到libwebsockets.org:80的 TCP 连接,此时 lws 尚不知情;
  • LWS_CALLBACK_RAW_ADOPT:收养完成,raw wsi 创建并绑定到raw-test协议;
  • LWS_CALLBACK_RAW_RX (186):收到 186 字节响应,内容正是HTTP/1.1 301 Redirect(因为目标站 80 端口会 301 跳转到 https),随后逐行 hexdump;
  • LWS_CALLBACK_RAW_CLOSE:约 5 秒后服务端超时断开,raw wsi 关闭。

值得注意的是 README 的提示:示例程序自己完成了所有连接工作,服务端在空闲 5 秒后关闭连接,之后程序继续运行直到你按^C退出。这说明 raw socket 被关闭后并不影响整个 context 的运行,这正体现了"外部连接纳入 lws 生命周期管理"的设计。

四、核心 API:lws_adopt_descriptor_vhost() 及其参数

4.1 函数签名与参数

示例中实际调用的核心 API 声明在 include/libwebsockets/lws-adopt.h:

struct lws * lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lws_sock_file_fd_type fd, const char *vh_prot_name, struct lws *parent);
参数说明
vh要归属的 lws vhost,新 wsi 将挂在这个 vhost 下
typelws_adoption_type的 OR 组合标志(见下节)
fd联合体lws_sock_file_fd_type,设置.sockfd(套接字)或.filefd(文件描述符)
vh_prot_nameNULL 或 vhost 上已注册的协议名,raw 连接会绑定到该协议
parentNULL 或已有 wsi,新 wsi 将作为其子 wsi 挂入父 wsi 的 child 列表

调用成功后返回新创建的 wsi;失败则关闭传入的 fd 并返回 NULL(这是 API 契约:一旦你决定收养,fd 的所有权就转移给 lws,失败路径下 lws 负责清理)。

4.2 lws_adoption_type 标志位

同样定义在 lws-adopt.h:

typedef enum { LWS_ADOPT_RAW_FILE_DESC = 0, /* convenience constant */ LWS_ADOPT_HTTP = 1, /* flag: absent implies RAW */ LWS_ADOPT_SOCKET = 2, /* flag: absent implies file */ LWS_ADOPT_ALLOW_SSL = 4, /* flag: use tls */ LWS_ADOPT_FLAG_UDP = 16, /* flag: socket is UDP */ LWS_ADOPT_FLAG_RAW_PROXY = 32, /* flag: raw proxy */ LWS_ADOPT_FLAG_NO_NONBLOCK = 64, /* flag: file fd can't be made nonblocking */ LWS_ADOPT_RAW_SOCKET_UDP = LWS_ADOPT_SOCKET | LWS_ADOPT_FLAG_UDP, } lws_adoption_type;

要点解读:

  • LWS_ADOPT_SOCKET是判断套接字还是文件的关键:置位表示收养的是 socket(fd 会被强制设为非阻塞,见 lib/io/adopt.c),缺省则按文件描述符处理;
  • 缺省不带LWS_ADOPT_HTTP即为 RAW 模式:raw-skt 角色正是靠"不是 HTTP 且是 socket"这一组合完成匹配(见下文源码分析);与之对比,lws_adopt_socket_vhost()内部使用的是LWS_ADOPT_SOCKET | LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL(lib/io/adopt.c),表示按 HTTP 服务模式收养、可接受升级为 ws 或提供 HTTP 服务;
  • LWS_ADOPT_RAW_SOCKET_UDP预置组合用于 UDP raw socket;
  • 该枚举还支持 RAW 文件描述符收养(minimal-raw-file示例),此时回调家族变为LWS_CALLBACK_RAW_ADOPT_FILE/RAW_RX_FILE/RAW_WRITEABLE_FILE/RAW_CLOSE_FILE,定义于 lws-callbacks.h。

4.3 增强版:lws_adopt_desc_t 与 via_info 变体

除了五参数版本,lws 还提供了结构化参数版本(同样声明于 lws-adopt.h):

typedef struct lws_adopt_desc { struct lws_vhost *vh; /* vhost the wsi should belong to */ lws_adoption_type type; /* OR-ed combinations of flags */ lws_sock_file_fd_type fd; /* union: .sockfd or .filefd */ const char *vh_prot_name; /* NULL or vh protocol name */ struct lws *parent; /* NULL or parent wsi */ void *opaque; /* opaque pointer to set on created wsi */ const char *fi_wsi_name; /* Fault Injection 继承过滤器 */ } lws_adopt_desc_t; struct lws *lws_adopt_descriptor_vhost_via_info(const lws_adopt_desc_t *info);

lws_adopt_descriptor_vhost()在实现上就是构造这个结构体后调用 via_info 变体(lib/io/adopt.c)。via_info 版本额外支持opaque(设置到新 wsi 的用户数据)与fi_wsi_name(Fault Injection 规则继承,用于测试场景)。

4.4 同类 API 对比

API用途
lws_adopt_socket(context, fd)按 HTTP 模式收养,挂到 context 第一个 vhost
lws_adopt_socket_vhost(vh, fd)按 HTTP 模式收养,挂到指定 vhost
lws_adopt_descriptor_vhost(vh, type, fd, prot, parent)通用收养,RAW/HTTP/socket/file 皆可
lws_adopt_socket_readbuf(vh, fd, readbuf, len)收养并指定必须先消费的已读缓冲(readbuf 上限 2048 字节,对应 ah rx 缓冲大小)
lws_create_adopt_udp(vhost, ads, port, ...)创建、绑定并收养 UDP socket

五、示例代码逐段拆解

5.1 注册 raw 协议与回调

static int callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { switch (reason) { case LWS_CALLBACK_RAW_ADOPT: lwsl_user("LWS_CALLBACK_RAW_ADOPT\n"); lws_callback_on_writable(wsi); /* 请求可写通知,触发后续写操作 */ break; case LWS_CALLBACK_RAW_CLOSE: lwsl_user("LWS_CALLBACK_RAW_CLOSE\n"); break; case LWS_CALLBACK_RAW_RX: lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len); lwsl_hexdump_level(LLL_NOTICE, in, len); break; case LWS_CALLBACK_RAW_WRITEABLE: if (lws_write(wsi, (uint8_t *)"GET / HTTP/1.1\xd\xa\xd\xa", 18, LWS_WRITE_RAW) != 18) { lwsl_notice("%s: raw write failed\n", __func__); return 1; } break; default: break; } return 0; } static struct lws_protocols protocols[] = { { "raw-test", callback_raw_test, 0, 0, 0, NULL, 0 }, LWS_PROTOCOL_LIST_TERM };

关键点:

  • LWS_CALLBACK_RAW_ADOPT在收养完成时触发,其语义等价于"wsi 已创建"(见 lws-callbacks.h 注释)。此时调用lws_callback_on_writable()注册可写兴趣,事件循环下一次就会投递LWS_CALLBACK_RAW_WRITEABLE;
  • LWS_CALLBACK_RAW_WRITEABLE中通过lws_write(wsi, buf, len, LWS_WRITE_RAW)发送原始字节。示例发送 18 字节的"GET / HTTP/1.1\r\n\r\n"(注意\xd\xa即\r\n),并检查返回值是否等于写入长度,不等则返回 1 要求关闭连接;
  • LWS_CALLBACK_RAW_RX中in指向收到的原始数据、len为长度。示例用lwsl_hexdump_level(LLL_NOTICE, ...)做 hexdump;
  • LWS_CALLBACK_RAW_CLOSE在连接关闭时收到,示例仅打印日志;
  • 协议回调返回非 0 表示请求关闭该 wsi。

5.2 创建 context 与"不监听"的 vhost

lws_context_info_defaults(&info, NULL); lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); ... info.port = CONTEXT_PORT_NO_LISTEN_SERVER; info.protocols = protocols; vhost = lws_create_vhost(context, &info);
  • LWS_SERVER_OPTION_EXPLICIT_VHOSTS表示 vhost 需要显式创建,避免 lws 自动创建默认 vhost;
  • CONTEXT_PORT_NO_LISTEN_SERVER是关键常量:这个 vhost 不创建任何 listen socket,纯粹作为被收养 wsi 的归属容器;
  • 程序还通过lws_cmdline_option_handle_builtin()解析命令行内置选项,例如-d <n>可调节调试日志级别(定义于源码顶部的lws_switches表)。

5.3 自行建立"外来"连接

lws_sock_file_fd_type sock; struct addrinfo h, *r, *rp; memset(&h, 0, sizeof(h)); h.ai_family = AF_UNSPEC; /* 允许 IPv4 或 IPv6 */ h.ai_socktype = SOCK_STREAM; h.ai_protocol = IPPROTO_TCP; n = getaddrinfo("libwebsockets.org", "80", &h, &r); ... for (rp = r; rp; rp = rp->ai_next) { sock.sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock.sockfd != LWS_SOCK_INVALID) break; } ... if (connect(sock.sockfd, rp->ai_addr, sizeof(*rp->ai_addr)) < 0) { ... }

这段代码完全绕开 lws,是标准的 POSIX 建连流程:getaddrinfo解析域名 →socket()创建 fd(遍历地址链表直到成功)→connect()建立连接。源码注释也点明了意图:

Normally you would do this with lws_client_connect_via_info() inside the lws event loop... But this example demonstrates how to integrate an externally-connected "foreign" socket, so we create one by hand.

注意sock的类型是lws_sock_file_fd_type——正是lws_adopt_descriptor_vhost()所要求的联合体,.sockfd成员直接承载我们刚建立的 fd。

5.4 收养:所有权移交的关键一步

if (!lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_SOCKET, sock, protocols[0].name, NULL)) { lwsl_err("%s: foreign socket adoption failed\n", __func__); goto bail; } while (n >= 0) n = lws_service(context, 0);
  • 第三个参数sock是持有已连接 fd 的联合体;
  • 第四个参数protocols[0].name即"raw-test",把 wsi 绑定到该协议;
  • 成功返回非 NULL wsi 后,进入lws_service(context, 0)事件循环;此后一切 IO 交给 lws 驱动;
  • 若收养失败返回 NULL,程序走bail:路径lws_context_destroy(context)收尾。注意此前 connect 的 fd 已按 API 契约由 lws 关闭,外部无需再自行 close。

六、源码级原理:收养在 lws 内部如何完成

6.1 调用链

lws_adopt_descriptor_vhost()的内部实现位于 lib/io/adopt.c,核心调用链为:

lws_adopt_descriptor_vhost(vh, type, fd, prot, parent) └─> lws_adopt_descriptor_vhost_via_info(&info) ├─> __lws_adopt_descriptor_vhost1(...) // 创建 wsi、绑定 vhost/协议、确定角色 ├─> getpeername(...) // 记录对端地址 └─> lws_adopt_descriptor_vhost2(...) // 设非阻塞、插入 fds 表、触发回调

6.2 阶段一:创建 wsi 并绑定角色

__lws_adopt_descriptor_vhost1()(adopt.c)完成:

  1. lws_create_new_server_wsi()创建新 wsi,若有parent则挂入父 wsi 的 child 列表;
  2. 若传入vh_prot_name,通过lws_vhost_name_to_protocol()找到协议并分配 user space;找不到则报 "Protocol %s not enabled" 并走失败路径;
  3. 调用lws_role_call_adoption_bind()让各角色尝试认领这个 wsi——raw-skt 角色的匹配逻辑就在这里。

raw-skt 角色的rops_adoption_bind_raw_skt()(lib/sansio/raw-skt/ops-raw-skt.c)判断条件为:

/* no http but socket... must be raw skt */ if ((type & LWS_ADOPT_HTTP) || !(type & LWS_ADOPT_SOCKET) || ...) return 0; /* no match */

即"不是 HTTP、且是 socket"的组合才会被 raw-skt 认领。这正是示例传LWS_ADOPT_SOCKET(不带LWS_ADOPT_HTTP)就能进入 raw 模式的根本原因。认领后绑定协议:优先使用传入的vh_prot_name,否则退到 vhost 的raw_protocol_index指定协议。

6.3 阶段二:非阻塞化并插入事件循环

lws_adopt_descriptor_vhost2()(adopt.c)完成:

  1. 强制非阻塞:对 socket 调用lws_plat_set_nonblocking(),这是 lws 事件循环工作的前提;
  2. 通过event_loop_ops->sock_accept()注册到当前事件循环后端(poll/libuv/libev 等,取决于编译配置的 event-lib);
  3. 调用__insert_wsi_socket_into_fds()把 fd 插入该 service 线程的 fds 表;
  4. 注释明确说明:回调被推迟到 fds 表插入之后才触发("by deferring callback to this point, after insertion to fds, lws_callback_on_writable() can work from the callback")——这就是在LWS_CALLBACK_RAW_ADOPT里能直接调用lws_callback_on_writable()的原因;
  5. 触发创建回调(raw-skt 服务端方向即LWS_CALLBACK_RAW_ADOPT,见 ops-raw-skt.c 的 adoption_cb 表),再调用lws_role_call_adoption_bind(..., _LWS_ADOPT_FINISH, ...)完成收尾。

失败路径的处理细节也值得注意:socket 收养失败会lws_close_free_wsi()并返回 NULL;而文件描述符收养失败时不会关闭 fd,因为 API 契约规定此时 fd 所有权仍归调用方(adopt.c)。

6.4 raw 数据通路

收养完成后,raw wsi 的数据流由 lib/sansio/raw-skt/ops-raw-skt.c 驱动:

  • rops_rx_raw_skt()(L78-L125):raw socket 的 sansIO 接收逻辑——"every byte goes to the user as RAW_RX",即所有收到的字节原样以LWS_CALLBACK_RAW_RX交给用户回调,*used = len表示全部消费;len == 0表示对端关闭,返回LWS_HPI_RET_PLEASE_CLOSE_ME进入关闭流程;若用户回调返回负数同样触发关闭;
  • rops_rx_policy_raw_skt()(L137-L172):读取策略——在有未发送完的缓冲时不读(LWS_RXPOL_HOLD),否则LWS_RXP_FORCE_READ强制读,读取上限为协议rx_buffer_size;
  • rops_handle_POLLOUT_raw_skt()(L175-L181):可写事件直接交给用户(LWS_HP_RET_USER_SERVICE),即LWS_CALLBACK_RAW_WRITEABLE;
  • 发送侧:用户在RAW_WRITEABLE中调用lws_write(wsi, buf, len, LWS_WRITE_RAW)写入原始字节流。

七、延伸:raw 家族的其他示例

理解了minimal-raw-adopt-tcp后,可以顺带对照 raw 系列的其他集成场景(minimal-examples-lowlevel/raw/README.md):

示例演示内容与本文的关系
minimal-raw-adopt-udp创建 UDP socket 并读写收养 API +LWS_ADOPT_RAW_SOCKET_UDP
minimal-raw-file收养文件描述符(设备节点、FIFO、文件等)同族 API,走LWS_CALLBACK_RAW_*_FILE回调
minimal-raw-netcatstdin 转发到远端服务器纯 raw 客户端
minimal-raw-vhostvhost 监听并接受 RAW 连接服务端 accept 侧,而非收养侧
minimal-raw-proxy/minimal-raw-proxy-fallbackraw 代理与 HTTP 回退结合LWS_ADOPT_FLAG_RAW_PROXY

这些示例共享同一套 raw 角色实现(lib/sansio/raw-skt/ops-raw-skt.c),只是接入点不同:收养(adopt)是把已经存在的连接纳入 lws,监听(vhost)是从lws 自己 accept 的连接开始,客户端(client)则是从lws_client_connect_via_info()开始。

八、常见问题与注意事项

  1. fd 所有权转移:lws_adopt_descriptor_vhost()调用后,无论成败,对 socket 而言 fd 都不再由你管理(成功由 lws 接管,失败由 lws 关闭)。不要在成功后继续用这个 fd 做读写,否则会造成与事件循环的竞争。
  2. 必须是非阻塞:lws 在收养阶段会强制设置非阻塞(lws_plat_set_nonblocking)。如果你的 fd 来自某个库且其内部假定阻塞语义,需要评估该 fd 上是否还有别的使用者。
  3. raw 连接没有协议语义:raw wsi 不做 HTTP 解析、不做分帧,收到的字节全部原样交给RAW_RX。示例中向 80 端口发了 HTTP 请求、收到 301 响应,纯粹是因为目标服务端是 HTTP 服务器——raw 层并不理解这些内容,也不会因此自动关闭连接,连接生命周期由对端行为或你的业务逻辑决定。
  4. 超时与空闲关闭:示例中 5 秒后服务端空闲超时断开,触发LWS_CALLBACK_RAW_CLOSE。若要主动控制连接存活,可结合lws_set_timeout()或你自己的业务定时器。
  5. 显式 vhost:收养需要先有一个 vhost 作为 wsi 的归属。示例用LWS_SERVER_OPTION_EXPLICIT_VHOSTS+CONTEXT_PORT_NO_LISTEN_SERVER创建了不监听的纯容器 vhost,这是集成场景的典型做法。

参考路径速查

  • 示例 README:minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/README.md
  • 示例源码:minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c
  • 构建脚本:minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/CMakeLists.txt
  • 收养 API 声明:include/libwebsockets/lws-adopt.h
  • 回调定义:include/libwebsockets/lws-callbacks.h
  • 收养实现:lib/io/adopt.c
  • raw-skt 角色实现:lib/sansio/raw-skt/ops-raw-skt.c
  • raw 示例总览:minimal-examples-lowlevel/raw/README.md
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

相关推荐

上一篇:四步上手 GBFR-Logs:为《碧蓝幻想:Relink》搭建免费 DPS 统计覆盖层的完整指南
下一篇:番茄小说下载器3步上手:把喜欢的书永久存进本地

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询