第 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 收缩读块 |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · net — 缓冲链迭代与流发送 API
- xrt 参考 · tcp_server — 监听模式与 Accept 语义