STTNet 0.7.0

第 8 章:原始 TCP

本章说明 TCP 字节流、消息边界以及基于 TcpServer 实现自定义协议的方式。

1. TCP 没有“消息”概念

TCP 是有序字节流。一次发送可能被拆成多次接收,多次发送也可能合并到一次接收。TcpInformation::data 只是当前读到的字节块,不保证等于一条业务消息。

自定义协议需要明确的消息边界,例如固定长度、长度前缀、分隔符或状态机;一次回调并不等同于一条完整消息。

2. 最小 Echo Demo

#include <sttnet.h>

#include <iostream>

class EchoTcpServer final : public stt::network::TcpServer
{
protected:
    // TcpServer 把心跳策略交给派生协议实现。
    // 这个字节回显 Demo 不需要应用层心跳。
    void handleHeartbeat() override {}
};

int main()
{
    using namespace stt::network;
    using stt::system::ServerSetting;

    if(!ServerSetting::blockTerminationSignals())
        return 1;

    EchoTcpServer server;

    // TCP 是字节流:一次回调只代表收到一段字节,
    // 可能只包含一条业务消息的一部分;实际协议需要增加
    // 固定头、长度前缀或分隔符等消息边界。
    server.setGlobalSolveFunction(
        [](TcpFDHandler &client, TcpInformation &info) {
            // 服务端回调中的 sendData() 会把字节提交到有界的
            // Reactor 发送队列,不会阻塞等待对端网络。
            return client.sendData(info.data) > 0;
        });

    if(!server.startListen(6060))
    {
        std::cerr << "failed to listen on port 6060\n";
        return 2;
    }

    std::cout << "TCP echo listening on 127.0.0.1:6060\n";
    ServerSetting::waitForTerminationSignal();
    return server.close() ? 0 : 3;
}

3. 默认 key 与全局回调

TcpServer 默认把当前数据作为 key。Echo 这类任意内容协议更适合使用 setGlobalSolveFunction()。有明确命令字时,可以在 setGetKeyFunction() 中解析并写入 info.ctx["key"]

4. 一个长度前缀协议的思路

// 线上协议示意:前 4 字节为网络字节序 payload 长度。
// 1. 把新数据追加到连接级缓冲。
// 2. 缓冲不足 4 字节时等待下一次回调。
// 3. 读取长度并校验上限。
// 4. 缓冲达到 4 + length 后提取一条完整消息。
// 5. 可能一次读到多条消息,因此循环解析。

5. 测试

./build/examples/sttnet_tcp_echo
printf 'hello TCP' | nc 127.0.0.1 6060
预期:nc 收到相同字节。这个 Demo 只证明字节收发,不代表已经设计了可靠业务协议。