第 25 章 / 共 51 章
卷三 · 协议开发(卷末)
连接接管:XS_TAKEOVER 的第三条路
有些业务天生不属于"一问一答":SOCKS 代理、升级后的私有协议、长轮询网关。XS_TAKEOVER 让 RequestProc 把整条连接接过来自己经营。
本章导读
站位:卷三收官。接管是三态里最重的一档——它把"驱动管连接、你管内容"的默认分工反转为"你管连接"。用对场景(协议升级/隧道),它是唯一解;用错场景,它是不必要的复杂度。
25.1 引入
三态的边界在第 4 章画过:XS_OK 交还连接继续 keep-alive、XS_FALLBACK 交给静态层。第三态 XS_TAKEOVER 的语义是"这条连接从现在起归我"——驱动撤出事件处理,之后的收发、超时、关闭全部由应用逻辑决定。典型场景:HTTP Upgrade 到私有协议、代理隧道、需要跨请求长会话的推送网关。
25.2 接管语义
RequestProc ──▶ XS_TAKEOVER
│
┌────────────┴────────────┐
│ 驱动:撤出 keep-alive 循环 │ /* 不再解析后续请求 */
│ 应用:换事件表 + 自行收发 │ /* pull / Future / 线程 */
│ 终态:应用最终 Close │
└────────────┬────────────┘
▼
Close 终态由 XServer Destroy 并释放 generation lease
- 接管点:从 RequestProc 返回 TAKEOVER 那一刻起,连接不再被 HTTP 驱动消费
- 事件表:应用接管后换成自己的事件处理(不得替换为非法状态)
- 回收契约:连接对象最终仍由 XServer Destroy——你经营它,但不拥有它的析构;Close 终态同时释放该连接持有的代租约
- 字节同步:接管前你已消费/写出的字节仍受驱动核对——半截 HTTP 请求 + TAKEOVER 的组合会被一致性校验拒绝
25.3 pull 模式实战
func_test 里有一个接管回显的真实用例,模式如下(异步拉取收发):
接管 + 异步回显(用例模式)模式对照 tools/func_test.py 的 takeover 用例
typedef struct TakeoverCtx {
XS_HostInfo* pHost; /* 需要跨线程时按所有权规则处理 */
xnetstream* tcp; /* 或 tls:接管后的传输层 */
} TakeoverCtx;
static XS_RequestResult RequestProc(XS_HttpReq* pReq)
{
if ( !IsUpgradeTarget(pReq) ) return XS_FALLBACK;
/* 1. 先写出协议要求的应答字节(如 101 Switching Protocols)*/
/* 2. 建接管上下文 */
TakeoverCtx* pCtx = xrtCalloc(1, sizeof(TakeoverCtx));
pCtx->tcp = pReq->tcp;
/* 3. 挂异步读:数据到达进回调(测试用例用独立线程 + RecvAsync)*/
StartTakeoverLoop(pCtx);
return XS_TAKEOVER; /* 连接归我 */
}
刚才发生了什么:接管循环里你对 pCtx->tcp 用普通 xrt 流 API 收发——就像它在 ch23 里那样。区别只是这条流的"出身"是 HTTP 连接,而你决定它后续说什么协议。
25.4 终态与回收
- 你必须最终 Close:接管即承诺——忘记关闭的连接会一直占着(idle_timeout 是兜底而非设计路径)
- Close 走流 API 的关闭语义;XServer 在连接终态回调里 Destroy 并释放代租约——所以旧代能等接管连接全部结束才回收(reload_matrix 验证过这个时序)
- 接管上下文(pCtx)的释放跟着 Close 走——与 WS/TCP 注册表同构的清理模式
先问"真的需要接管吗":普通业务(哪怕 SSE/长轮询)用 chunked + 常规回调就能做(ch15 练习)。接管留给协议切换与隧道——它是逃生舱,不是日常工具。
25.5 契约
- 接管线:返回 TAKEOVER 前必须完成协议应答字节;接管后驱动不再消费该连接。
- 终态线:应用负责最终 Close;Destroy 与租约释放由 XServer 在终态完成。
- 上下文线:接管上下文生命周期绑定连接终态,注册表式清理。
常见错误
接管后忘记 Close
- 症状
- 连接表缓慢增长;旧代迟迟不回收(租约未归还)。
- 原因
- 接管承诺未兑现——每个 TAKEOVER 都必须有一条 Close 路径(含错误分支)。
半截请求就接管
- 症状
- 连接被立即断开。
- 原因
- 驱动核对"脚本声明消费的字节 vs 线路实际"——还没消费完请求字节就 TAKEOVER 属于不一致状态。先消费干净再接管。
编程练习
基础接管回显复现
按 25.3 模式实现 /raw:接管后进入行回显模式(HTTP 层面像"升级"了协议)。
验收:curl 保持连接发送任意行得到回显;断开后无泄漏(连接表归零)。
进阶SOCKS5 迷你代理
实现 CONNECT 方法:接管连接后与目标建第二条流做双向搬运(xrt 双流转发)。
验收:curl -x 走你的代理访问任一 HTTP 站;任一侧断开两侧都干净关闭。
挑战接管期热重载
保持一个接管连接活跃,提交脚本 reload,验证旧代等接管连接结束才回收。
验收:接管连接持续工作;日志显示旧代 finalize 发生在连接关闭之后。
小结
| 要点 | 结论 |
|---|---|
| 第三态 | XS_TAKEOVER:驱动撤出,应用经营整条连接 |
| 场景 | 协议升级 / 隧道 / 代理——不是日常业务工具 |
| 接管纪律 | 先写完协议应答字节;消费核对仍然生效 |
| 终态纪律 | 必须最终 Close;Destroy 与租约释放由 XServer 完成 |
| 卷三结 | 四类协议 + 路由/JSON/模板/vhost 全部在手,进入卷四热重载 |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · future — 异步收发的完成通知模型
- xrt 参考 · proxy — SOCKS5 / HTTP CONNECT 的库层实现(练习进阶参考)