第 23 章 / 共 51 章
卷三 · 协议开发

TCP 流服务:裸字节世界的三个回调

游戏网关、IoT 接入、私有协议——它们不说 HTTP。tcp/tcps 类把一条裸流交给你,其余全部由你定义。

本章导读

站位:比 HTTP 更简单的回调面(只有三个),但更 raw 的数据形态(缓冲链)。本章结尾与 ch33(XTP2)衔接:TCP 之上自组协议正是实战二的主题。

23.1 引入

HTTP 的价值是"约定了请求长什么样";TCP 的价值是"什么都没约定"。于是宿主对 TCP 的职责收缩到三件事:建流、给数据、报告终态——协议设计权完整地留给你。TLS(tcps)只是把流的加密层接上,回调面完全一致。

23.2 视图与回调

三个回调 + 视图(契约原文风格)
/* XS_StreamConn:tcp 与 tls 二选一非空(tcps 下禁止绕过 tls 直写)*/
static void EventOpen(XS_HostInfo* pHost, XS_StreamConn* pConn);
static void EventData(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetbuf* pBuffer);
static void EventClose(XS_HostInfo* pHost, XS_StreamConn* pConn,
    xnetresult iResult, const xerror* pError);
  • EventOpen:连接建立——发欢迎横幅、建会话上下文的时机
  • EventData:数据到达。pBuffer 是零拷贝缓冲链(回调内借用):尺寸 xrtNetBufSize(pBuffer),数据经 xrt 缓冲迭代 API 消费——它不是一块连续内存,别当 char* 用
  • EventClose:终态——iResult 给网络层结论,pError 给错误详情(可为 NULL)
  • 发送:xrtNetStreamSend(pConn->tcp, ...) / xrtTlsStreamSend(pConn->tls, ...) 按非空一侧

23.3 横幅与 echo

最小 TCP 服务
static void EventOpen(XS_HostInfo* pHost, XS_StreamConn* pConn)
{
    if ( pConn->tcp != NULL )
        (void)xrtNetStreamSend(pConn->tcp, "hello\n", 6);
    /* tcps:改判 pConn->tls + xrtTlsStreamSend(查 written)*/
}

static void EventData(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetbuf* pBuffer)
{
    /* echo:把收到的数据原路发回。
     * 真实实现:遍历缓冲链的每个 block 逐段 Send;
     * 尺寸查询:xrtNetBufSize(pBuffer) */
}
命令行 · 实机形态
$ telnet 127.0.0.1 9000
hello
(你输入的每行被原样回显)

刚才发生了什么:连接即横幅是很多文本协议(SMTP/FTP/IRC)的惯例;echo 是流服务的"hello world"。真实协议(帧头 + 长度 + 体)在此之上加一个状态机——ch33 的 XTP2 就是现成的帧库。

23.4 生命周期与限额

  • 连接生命周期宿主持有:Accept 接管 → 注册表 → Close 时析构——应用不 Destroy 连接(与 custom 类"一切手动"的区别)
  • SNI:tcps 下 host 级证书构建 SNI 表,每连接按 SNI 出证书
  • idle_timeout:空闲到点服务端主动关闭(func_test 有 4.5s 关闭的实测用例)
  • recv_limit:小于默认读块时同步收缩读尺寸(避免配置引发泛化失败)
  • 会话上下文:与 WS 同构——自建注册表(连接 → 上下文),Open 建、Close 删

23.5 契约

  • 借用线:pConn/pBuffer/pError 回调内有效;缓冲链数据当帧消费,不当持久存储。
  • 方向线:tcps 下禁止绕过 tls 直写 tcp——明文字节会被对端当作 TLS 记录噪音。
  • 生命周期线:连接归宿主;你的会话上下文归你,Close 终态清理。

常见错误

把 xnetbuf 当连续内存
症状
读到的数据错乱 / 越界。
原因
它是缓冲链(Head/Tail/Size 字段的结构)——消费必须走 xrt 的缓冲迭代 API 按块读取,没有"整体 data 指针"。
跨回调保存 pConn 后自行收发
症状
擅自拿连接指针在其他线程收发后崩溃或数据错序。
原因
回调外的主动收发属于"接管"模式(HTTP 的 XS_TAKEOVER / 自建线程需谨慎);常规模式下你的逻辑应该由事件驱动——Open/Date/Close 三拍里完成。

编程练习

基础横幅 echo 服务

实现 23.3 的服务并 telnet 验证。

验收:连接即见 hello;输入回显;Ctrl+] 退出后 EventClose 日志可见。
进阶行协议解析器

实现按 \n 分帧的行协议(跨多次 EventData 的半行缓冲),命令 GET/SET/QUIT 的迷你 KV。

验收:分两次发送的半行命令("GE"+"T key\n")能正确解析。
挑战tcps 双栈

给服务开 TLS 端口与证书,同一套回调服务明文与加密两个端点。

验收:telnet 走明文、openssl s_client 走加密,行为一致;日志可见两条端点 bound。

小结

要点结论
回调面Open/Date/Close 三拍;协议设计权全在你
数据形态xnetbuf 零拷贝缓冲链,回调内借用按块消费
tcps视图二选一;禁止绕过 tls;SNI 每连接出证书
生命周期连接宿主管;上下文你管(注册表 + Close 清理)
限额idle_timeout 空闲关闭;recv_limit 收缩读块
xrt 延伸阅读