第 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 全部在手,进入卷四热重载
xrt 延伸阅读