libwebsockets 原始套接字收养(RAW Socket Adoption)实战:把外部 TCP 连接交给 lws 事件循环
后端网络通信【免费下载链接】libwebsocketscanonical 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 APIsocketgetaddrinfoconnect或第三方网络库建立连接上层业务逻辑在建立连接前后做了自定义处理如特定握手、代理协商你希望保留这些已连接 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创建 lws context 和一个不监听任何端口的 vhostCONTEXT_PORT_NO_LISTEN_SERVER并注册一个名为raw-test的协议用原生getaddrinfo()socket()connect()自行连接到libwebsockets.org:80——这一步完全不经过 lws模拟外部程序已经建好连接的现实场景调用lws_adopt_descriptor_vhost()把这个外来 socket 收养为 raw wsi绑定到raw-test协议lws 接管后在回调里向远端写入GET / HTTP/1.1\r\n\r\n并把收到的响应做 hexdump 打印由于这是一个不懂 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-age1576800 [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创建了不监听的 vhost7681 只是默认的展示端口号实际没有 listen socketStarting 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随后逐行 hexdumpLWS_CALLBACK_RAW_CLOSE约 5 秒后服务端超时断开raw wsi 关闭。值得注意的是 README 的提示示例程序自己完成了所有连接工作服务端在空闲 5 秒后关闭连接之后程序继续运行直到你按^C退出。这说明 raw socket 被关闭后并不影响整个 context 的运行这正体现了外部连接纳入 lws 生命周期管理的设计。四、核心 APIlws_adopt_descriptor_vhost() 及其参数4.1 函数签名与参数示例中实际调用的核心 API 声明在 include/libwebsockets/lws-adopt.hstruct 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.htypedef 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 cant be made nonblocking */ LWS_ADOPT_RAW_SOCKET_UDP LWS_ADOPT_SOCKET | LWS_ADOPT_FLAG_UDP, } lws_adoption_type;要点解读LWS_ADOPT_SOCKET是判断套接字还是文件的关键置位表示收养的是 socketfd 会被强制设为非阻塞见 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_SSLlib/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.htypedef 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_nameFault Injection 规则继承用于测试场景。4.4 同类 API 对比API用途lws_adopt_socket(context, fd)按 HTTP 模式收养挂到 context 第一个 vhostlws_adopt_socket_vhost(vh, fd)按 HTTP 模式收养挂到指定 vhostlws_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_WRITEABLELWS_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, ...)做 hexdumpLWS_CALLBACK_RAW_CLOSE在连接关闭时收到示例仅打印日志协议回调返回非 0 表示请求关闭该 wsi。5.2 创建 context 与不监听的 vhostlws_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 自动创建默认 vhostCONTEXT_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完成lws_create_new_server_wsi()创建新 wsi若有parent则挂入父 wsi 的 child 列表若传入vh_prot_name通过lws_vhost_name_to_protocol()找到协议并分配 user space找不到则报 Protocol %s not enabled 并走失败路径调用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完成强制非阻塞对 socket 调用lws_plat_set_nonblocking()这是 lws 事件循环工作的前提通过event_loop_ops-sock_accept()注册到当前事件循环后端poll/libuv/libev 等取决于编译配置的 event-lib调用__insert_wsi_socket_into_fds()把 fd 插入该 service 线程的 fds 表注释明确说明回调被推迟到 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()的原因触发创建回调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-L125raw 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_sizerops_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_UDPminimal-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()开始。八、常见问题与注意事项fd 所有权转移lws_adopt_descriptor_vhost()调用后无论成败对 socket 而言 fd 都不再由你管理成功由 lws 接管失败由 lws 关闭。不要在成功后继续用这个 fd 做读写否则会造成与事件循环的竞争。必须是非阻塞lws 在收养阶段会强制设置非阻塞lws_plat_set_nonblocking。如果你的 fd 来自某个库且其内部假定阻塞语义需要评估该 fd 上是否还有别的使用者。raw 连接没有协议语义raw wsi 不做 HTTP 解析、不做分帧收到的字节全部原样交给RAW_RX。示例中向 80 端口发了 HTTP 请求、收到 301 响应纯粹是因为目标服务端是 HTTP 服务器——raw 层并不理解这些内容也不会因此自动关闭连接连接生命周期由对端行为或你的业务逻辑决定。超时与空闲关闭示例中 5 秒后服务端空闲超时断开触发LWS_CALLBACK_RAW_CLOSE。若要主动控制连接存活可结合lws_set_timeout()或你自己的业务定时器。显式 vhost收养需要先有一个 vhost 作为 wsi 的归属。示例用LWS_SERVER_OPTION_EXPLICIT_VHOSTSCONTEXT_PORT_NO_LISTEN_SERVER创建了不监听的纯容器 vhost这是集成场景的典型做法。参考路径速查示例 READMEminimal-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.craw-skt 角色实现lib/sansio/raw-skt/ops-raw-skt.craw 示例总览minimal-examples-lowlevel/raw/README.md赞分享后端网络通信【免费下载链接】libwebsocketscanonical libwebsockets.org networking library项目地址https://gitcode.com/gh_mirrors/li/libwebsockets点击查看免费下载相关推荐libwebsockets 原始套接字接管Socket Adoption实战lws-minimal-raw-adopt-tcp 详解libwebsockets 原始套接字接管Socket Adoption实战lws minimal raw adopt tcp 详解 导读 本文围绕 TE人工智能AI Agent多模态语音AI 应用libwebsockets 实战lws-minimal-raw-adopt-udp——把外部 UDP socket 收编进 lws 事件循环做 RAW 回显libwebsockets 实战lws minimal raw adopt udp——把外部 UDP socket 收编进 lws 事件循环做 RAW 回后端网络通信libwebsockets 实战用 lws_create_adopt_udp 将外部 UDP 套接字接入事件循环minimal-raw-adopt-udp 深度解析libwebsockets 实战用 lws_create_adopt_udp 将外部 UDP 套接字接入事件循环minimal raw adopt udp人工智能AI Agent多模态语音AI 应用上一篇四步上手 GBFR-Logs为《碧蓝幻想Relink》搭建免费 DPS 统计覆盖层的完整指南下一篇番茄小说下载器3步上手把喜欢的书永久存进本地创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

源师兄开发板电子指南针实战:磁力计数据读取、方位区间判断与 OLED 实时显示

源师兄开发板电子指南针实战:磁力计数据读取、方位区间判断与 OLED 实时显示

教程教育知识库 【免费下载链接】课程 本项目致力于成为跨学科知识的中枢,系统汇聚与持续孵化高质量的课程资源。内容覆盖计算机、人工智能、信息科技、人文教育等多个领域,旨在为学生、教师、自学者及所有终身学习者,构建一个结构清晰、易于…

2026/10/14 3:02:31 阅读更多 →
IEC 62061-2021机械安全应用:SIL定级与PFH计算落地指南

IEC 62061-2021机械安全应用:SIL定级与PFH计算落地指南

简介:IEC 62061-2021高清英文版是一份标准PDF文档,面向机械安全领域的设计工程师、功能安全评估人员、认证机构及工业机械使用者。该标准由IEC于2021年3月发布第二版,系统规定了安全相关电气、电子和可编程电子控制系统(SRP/CS&am…

2026/10/14 3:02:31 阅读更多 →
数据搬运与存储对接实战:rclone / juicesync 迁移 + 目录对账 + 容器多源挂载

数据搬运与存储对接实战:rclone / juicesync 迁移 + 目录对账 + 容器多源挂载

适用场景:AI 训练/推理集群、数十 TB 级数据集迁移、多套对象存储对接、单容器聚合多数据源 核心工具:rclone(通用)、juicesync(对象存储专用)、find diff 对账 核心纪律:迁移前先对账&#xf…

2026/10/14 3:02:31 阅读更多 →

最新新闻

一张图微调YOLOv11:自然栖息地鸟类检测的流程与扩充

一张图微调YOLOv11:自然栖息地鸟类检测的流程与扩充

简介:面向 YOLOv11 模型验证需求,这份精简版鸟类目标检测数据集包含一张真实野外鸟类图像及配套标准标注,可用于快速原型验证、轻量级部署测试与小样本检测基线建立。压缩包共五个文件,包含三张 JPG 图像、一个数据配置文件与一个…

2026/10/14 3:39:38 阅读更多 →
ASP.NET + SQLServer葡萄酒商城源码:从数据库还原到下单事务全攻略

ASP.NET + SQLServer葡萄酒商城源码:从数据库还原到下单事务全攻略

简介:一套基于ASP.NET与SQL Server的葡萄酒网上商城销售系统,面向ASP.NET WebForm学习者、毕业设计学生以及需要搭建在线商城教学演示的开发者。系统围绕普通会员、商户会员和管理员三类角色展开,覆盖商品管理、购物车、订单处理、发货、商户…

2026/10/14 3:39:38 阅读更多 →
LangChain项目代码拆解:Chain、Agent、Memory与Retrieval四大核心组件实战指南

LangChain项目代码拆解:Chain、Agent、Memory与Retrieval四大核心组件实战指南

简介:这是一份LangChain入门与实践的项目代码包,面向正在学习大模型应用开发的Python开发者,旨在帮助读者快速理解LangChain的模块化设计与核心用法。包内共3个文件,包含一个HTML格式的入门讲解页、一个项目运行配置文件以及一个版…

2026/10/14 3:39:38 阅读更多 →
C++学习日记 Day21:C++11(下)lambda表达式和function包装器

C++学习日记 Day21:C++11(下)lambda表达式和function包装器

## 今天学了什么今天学习C11的lambda表达式和function包装器。## 可调用对象概念:在C中有一类对象可像函数一样调用;分类:①函数指针using prt void(*)(int, string); prt f1 fun; f1(1, "函数指针");②仿函数 --有 operator&am…

2026/10/14 3:39:38 阅读更多 →
一人公司AI工作流:6步实现内容生产去体力化

一人公司AI工作流:6步实现内容生产去体力化

1. 一人公司的内容困局与破局思路一个人做内容,最怕的不是没灵感,而是灵感来了却卡在“执行”上。写一篇长文要三小时,做一张配图要半小时,剪一条短视频要一整个下午,排版发布又要来回切换四五个平台。一天下来&#x…

2026/10/14 3:39:38 阅读更多 →
8765平台前后摄DVDD电压不一致排查与修复指南

8765平台前后摄DVDD电压不一致排查与修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 3:38:38 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →