Skip to content

Commit 1804459

Browse files
authored
feat: add compat.websocket 12.0.1 (IXWebSocket RFC 6455 client, zero deps) (#158)
1 parent 567ea56 commit 1804459

7 files changed

Lines changed: 864 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# 新增 compat.websocket 12.0.1(2026-08-06)
2+
3+
产出:`compat.websocket@12.0.1`(IXWebSocket,上游 MIT),32 个 client TU 的 header-compat
4+
Form B 包,workspace 成员 `tests/examples/websocket`,以及一份自带 echo server 的离线测试。
5+
6+
## 1. 形态判定:C++ 源码 compat,纯客户端,零依赖
7+
8+
IXWebSocket 上游**没有 mcpp 支持**(CMake 构建、C++11、`#include <ixwebsocket/IXWebSocket.h>`
9+
的头文件式消费),属第三方上游库 → Form B。上游源码是扁平的 `ixwebsocket/` 目录,头文件与源码同处一室;
10+
`include_dirs = { "*" }` 把 verdir 根暴露出去,消费端 include 路径与上游安装布局一致
11+
(`<includedir>/ixwebsocket/…`)。
12+
13+
**编译开关三项全关,是零外部依赖的关键**:
14+
15+
- **TLS 关**(不定义 `IXWEBSOCKET_USE_TLS`):TLS socket 三组 TU(OpenSSL/MbedTLS/AppleSSL)根本不编入;
16+
`IXSocketFactory.cpp` 只在 `#ifdef IXWEBSOCKET_USE_TLS` 下引用它们。计划稿"关掉 TLS 就不拉 OpenSSL"由此兑现。
17+
- **zlib 关**(不定义 `IXWEBSOCKET_USE_ZLIB`):`IXGzipCodec.cpp` 与 per-message-deflate codec 的所有
18+
zlib 调用都包在这个宏后,不定义则编译为透传 no-op —— 客户端在握手里不再提议压缩扩展,是保守且合法的默认。
19+
- **server 不提供**(见下节)。
20+
21+
**源列表 = 上游 CMake `IXWEBSOCKET_SOURCES` 的 36 项去掉 4 个 server TU**,余 32 项逐条列出
22+
(与 `compat.zlib`/`compat.protobuf` 的显式列法一致;`compat.abseil` 用通配是因为上游文件命名可裁剪,
23+
这里没有可裁剪的通配,显式列反而可审)。
24+
25+
## 2. server TU 排除有链接层论证,不是拍脑袋
26+
27+
剔除的四个 TU:`IXWebSocketServer.cpp``IXWebSocketProxyServer.cpp``IXHttpServer.cpp`
28+
`IXSocketServer.cpp`。排除前先在整个 client 侧做了符号引用检查 —— 除这 4 个文件自身外,**没有任何
29+
保留的 `.cpp` 引用这些类名**(`grep -l 'IXWebSocketServer\|IXHttpServer\|IXSocketServer\|IXWebSocketProxyServer'
30+
` 只命中那 4 个文件自己)。这是"client 不反依赖 server"的直接证据,链接期不会 undefined reference。
31+
32+
`IXGetFreePort.cpp` **保留**:它是独立工具(找空闲端口),不属于 server 那一侧,且 32 个保留 TU 的
33+
basename 全局唯一,不会触发 mcpp#233/#240 的 flat-obj 撞名。
34+
35+
## 3. 关键决定(含两处与计划稿的偏离)
36+
37+
|| 计划稿 | 本包实际 | 理由 |
38+
|---|---|---|---|
39+
| 版本 | 未指定 | `12.0.1` | 上游最新稳定 tag(`git ls-remote --tags``sort -V \| tail`) |
40+
| 目录 | `pkgs/w/` | **`pkgs/c/`** | 仓库约定是**完整包名首字母**(`compat.*``c/`,见 skill 与 docs);`pkgs/e/``compat.eui-neo` 是唯一用短名首字母的例外。放错目录会让本地 path index 报 not found |
41+
| `language` | `c++17` | **`c++23`** | 全仓 59 个描述符统一 `c++23`(本仓 floor);IXWebSocket 是 C++11 源码,在 CI 工具链 clang 22.1.8 的 `c++23`**32/32 TU 零错误**通过,无 char8_t/弃用 API 问题,没必要为它开 c++17 特例 |
42+
| CN 镜像 | GLOBAL+CN 双 mirror | **plain-string url** | 本机无 `gtc`/gitcode 配置、无 `mcpp-res` 写权限,按 [docs/cn-mirror.md](../../docs/cn-mirror.md) 回退为纯字符串上游 url(与 `eui-neo` 0.5.5 同做法);sha 不受影响,维护者有权限后补 `{ GLOBAL, CN }` 即可 |
43+
44+
## 4. 平台开关
45+
46+
- linux:`-lpthread`(上游 `Threads::Threads`);无 `-ldl`(全库无 `dlopen`/`dlsym`)。
47+
- macosx:`-lpthread`;TLS 关则无需 Foundation/Security。
48+
- windows:`-lws2_32 -lwsock32` + `-D_CRT_SECURE_NO_WARNINGS``shlwapi` **不链**:它只被剔除的
49+
`IXSocketOpenSSL.cpp` 用到(其 `PathFileExists` 等);保留的 TU 里没有任何 shlwapi 引用。`NOMINMAX`
50+
也不需要:唯一 include `windows.h` 的保留 TU 是 `IXSetThreadName.cpp`,其源码不用 min/max。
51+
52+
## 5. 测试设计:自带最小 RFC 6455 echo server,全程离线
53+
54+
包只编 client,测试要实测"握手/掩码/分片/关闭"就不能依赖外部服务。`tests/examples/websocket/tests/ws_test.cpp`
55+
**进程内**用原始 loopback socket 起了一个最小 echo server,并刻意做成**与被测库相互独立**:
56+
57+
- `Sec-WebSocket-Accept` 用测试自带的 SHA-1 + base64 计算,`HTTP/1.1 101` 响应手写;
58+
- 帧协议手写(掩码位/长度扩展/控制帧),server 端能验证**客户端的掩码位确实置位**;
59+
- 收到特殊载荷 `FRAG` 时回一条**分片消息**(text FIN=0 + 两个 continuation),验证客户端重组;
60+
- 收到 `PINGME` 时回一条 ping,验证客户端默认 `enablePong` 的自动 pong;
61+
- close 回显 code 后关闭连接。
62+
63+
断言 11 项,全部带超时:`initNetSystem`、握手打开、text echo、**客户端掩码位**、binary echo 逐字节、
64+
客户端 ping→pong、服务端 ping→客户端自动 pong、分片重组、分片帧先于重组消息、close 握手完成、
65+
server 收到 close。CI 三平台离线可跑,不依赖 runner 网络。
66+
67+
**两个实测踩坑**(都已修进最终测试):
68+
69+
1. **IXWebSocket 的 `_automaticReconnection` 默认是 `true`**(构造函数 `_automaticReconnection(true)`),
70+
不是关闭。close 握手完成后状态转 `Closed`,若测试在收到 Close 消息与 `ws.stop()` 之间留空窗,
71+
run 循环会**再次 `checkConnection` 并重连** —— 而本测试的 server 线程在 close 后已退出,重连的
72+
握手读响应会永久阻塞,表现为进程挂死。修法:close 测试直接调 `ws.stop()`(它内部先 `close()` 再置
73+
`_stop`),run 循环因 `_stop` 而退出,Close 消息在 `stop()` 返回前已投递到 collector。
74+
2. **"客户端掩码位"断言过早**:Open 消息之后客户端还没发过任何数据帧,server 看不到掩码位。把该断言
75+
挪到 text echo 之后(echo 成功 = server 必然已收到客户端数据帧)。
76+
77+
## 6. 验证结论
78+
79+
两轮验证都通过:
80+
81+
**与 CI 完全一致的一轮(mcpp 2026.8.3.3 + gcc@16.1.0 + `MCPP_INDEX_MIRROR=GLOBAL` +
82+
`MCPP_BUILD_CACHE=local`,冷删 `target/`/`.mcpp/`):**
83+
84+
```
85+
mcpp xpkg parse pkgs/c/compat.websocket.lua → parse OK(compat.websocket / 三平台 12.0.1 /
86+
c++23 / import_std=false / 32 sources / target websocket)
87+
mcpp test -p websocket → test result ok. 1 passed; 0 failed
88+
```
89+
90+
该轮实际下载了 CI 的 gcc@16.1.0 工具链并以之编译 compat.websocket 的 32 个 TU,再编译并运行
91+
`ws_test` —— **11 项断言全部通过**,`ALL WEBSOCKET ASSERTIONS PASSED`,exit 0。
92+
93+
**独立的一轮(clang 22.1.8,standalone):** 32/32 TU 在 `c++23` 下逐 TU 编译零错误;同一测试二进制的
94+
11 项断言全过。
95+
96+
lint:`lua` 语法、`check_mirror_urls`(plain-string url 不触发镜像约束)、`check_package_name` 全过;
97+
`mcpp xpkg parse` 在本地较新 mcpp(2026.8.4.1)与 CI pin(2026.8.3.3)上均 OK。
98+
99+
## 7. 后续待办
100+
101+
- **CN 镜像**:有 `mcpp-res` 写权限后,`gtc``mcpp-res/websocket`、传与 GLOBAL 字节一致的
102+
`IXWebSocket-12.0.1.tar.gz`,把三平台 url 改写为 `{ GLOBAL, CN }`(sha 不变)。
103+
- **TLS feature**(可选):索引里有 `compat.openssl`/`compat.mbedtls`,若要支持 wss,可加 feature 把对应
104+
TLS TU 与依赖编入 —— 本包刻意先做零依赖纯客户端。
105+
- **C++23 薄封装头**(可选):用户计划稿提到的 `websocket.hpp`(span/format/RAII 薄层)不阻塞本包,可后续单独加。

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Two kinds of packages live here:
4040
| Native module library (Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua) (module layer; sources compiled directly through `compat.ffmpeg`) · [`opencv`](pkgs/o/opencv.lua) (single repository: the module layer and the full OpenCV 5 source build both live in the package, and only this descriptor stays on the index side) · [`mcpplibs.grpc`](pkgs/g/grpc.lua) (gRPC 1.83.0 — the one library here that CANNOT be a compat descriptor: upstream publishes no self-contained source artifact, its tag archive carrying abseil/protobuf/re2/boringssl/zlib as empty submodule placeholders, so [grpc-m](https://github.com/mcpplibs/grpc-m)'s release tarball IS that artifact. It vendors only gRPC's own source and takes the five dependencies from this index, so a consumer that also uses protobuf links one copy rather than two) |
4141
| C-source compat (with `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
4242
| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) |
43+
| C++-source compat, client-only with zero deps | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs. TLS is off (no `IXWEBSOCKET_USE_TLS`; the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and zlib is off too (`IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op) — no external dependency at all. Its test brings its own minimal RFC 6455 echo server on loopback sockets, so the handshake, masking, fragmentation and close are exercised offline) |
4344
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4445
| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4546
| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) |

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
3737
| 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](pkgs/o/opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) · [`mcpplibs.grpc`](pkgs/g/grpc.lua)(gRPC 1.83.0 —— 本索引里唯一**无法**做成 compat 描述符的库:上游不发布任何自包含源码产物,其 tag 归档里 abseil/protobuf/re2/boringssl/zlib 全是空 submodule 占位,因此 [grpc-m](https://github.com/mcpplibs/grpc-m) 的 release tarball 才是那个产物。它只 vendor gRPC 自己的源码,五个依赖全取自本索引,故同时直接使用 protobuf 的消费者链进去的是同一份而非两份)|
3838
| C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
3939
| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) |
40+
| C++ 源码 compat(纯客户端,零依赖) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端。TLS 关闭(不定义 `IXWEBSOCKET_USE_TLS`,OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),zlib 也关闭(`IXWEBSOCKET_USE_ZLIB` 不定义,gzip codec 编译为 no-op),于是零外部依赖。其测试自带一个基于 loopback 原始 socket 的最小 RFC 6455 echo server,握手/掩码/分片/关闭全部离线实测) |
4041
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4142
| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4243
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) |

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ members = [
6262
"tests/examples/spdlog-compiled",
6363
"tests/examples/tinyhttps",
6464
"tests/examples/vulkan",
65+
"tests/examples/websocket",
6566
"tests/examples/tray",
6667
"tests/examples/yyjson",
6768
"tests/examples/magic_enum",

0 commit comments

Comments
 (0)