协议栈与自定义协议

llbc 的网络层在每个 Session 上挂载一条协议栈(ProtocolStack),由若干协议层(ProtocolLayer)串联而成,负责完成字节流 ↔ 数据包的拆装,以及可选的压缩与编解码。通过实现 LLBC_IProtocolFactory 可以替换任意层的协议实现,也可以插入 LLBC_IProtocolFilter 对收发包做拦截过滤。

协议栈分层与收发方向

协议层枚举(LLBC_ProtocolLayer)

协议层按固定顺序排列,索引由 LLBC_ProtocolLayer 定义:

枚举值 索引 职责
PackLayer 0 字节流 ↔ LLBC_Packet 拆装(帧头解析)
CompressLayer 1 压缩 / 解压
CodecLayer 2 业务对象编解码(序列化/反序列化)

发送方向:CodecLayer → CompressLayer → PackLayer → 字节流 接收方向:字节流 → PackLayer → CompressLayer → CodecLayer

// 检查层合法性 / 获取层名称
LLBC_ProtocolLayer::IsValid(LLBC_ProtocolLayer::PackLayer);     // true
LLBC_ProtocolLayer::Layer2Str(LLBC_ProtocolLayer::CodecLayer);  // "CodecLayer"

ProtocolStack 的三种模式

LLBC_ProtocolStack 有三种 StackType:

框架内置两个开箱即用的工厂:

// Normal 模式:FullStack,框架自动处理帧头拆装 + 编解码
LLBC_Service *svc = LLBC_Service::Create("MyApp",
    new LLBC_NormalProtocolFactory);

// Raw 模式:PackStack only,收发裸字节块,业务自行解析
LLBC_Service *svc = LLBC_Service::Create("MyApp",
    new LLBC_RawProtocolFactory);

PacketProtocol 默认帧头格式(20 字节):

字段 偏移 长度
Length(含头) 0 4
Opcode 4 4
Status 8 2
Flags 10 2
ExtData1 12 8

替换 PackLayer 即可采用不同的帧格式。

IProtocol 接口

每个协议层实现 LLBC_IProtocol,核心方法只有三个:

class LLBC_EXPORT LLBC_IProtocol
{
public:
    virtual int  GetLayer() const = 0;
    // 发送方向:in/out 类型随层约定(见下文)
    virtual int  Send(void *in, void *&out, bool &removeSession) = 0;
    // 接收方向
    virtual int  Recv(void *in, void *&out, bool &removeSession) = 0;
    // 控制命令透传,返回 true 继续向下传递,返回 false 截断
    virtual bool Ctrl(int cmd, const LLBC_Variant &ctrlData, bool &removeSession);
};

removeSession 是输出参数:返回 -1 时若置为 true,框架将关闭该会话。

IProtocolFactory 与自定义协议

为每个连接(Listen / Connect 调用)指定工厂,工厂按层返回协议实例:

class MyProtoFactory final : public LLBC_IProtocolFactory
{
public:
    LLBC_IProtocol *Create(int layer) const override
    {
        switch (layer)
        {
            case LLBC_ProtocolLayer::PackLayer:
                return new MyPackProtocol();     // 自定义帧格式
            case LLBC_ProtocolLayer::CompressLayer:
                return new LLBC_CompressProtocol(); // 框架内置
            case LLBC_ProtocolLayer::CodecLayer:
                return new LLBC_CodecProtocol();    // 框架内置
            default:
                return nullptr;
        }
    }
};

将工厂传入 Listen / Connect / Create:

// 服务级别默认工厂(所有会话共享)
LLBC_Service *svc = LLBC_Service::Create("App", new MyProtoFactory);

// 单会话独立工厂(优先级高于服务级别工厂)
int sid = svc->Listen("0.0.0.0", 7788, new MyProtoFactory);

自定义 PackLayer 骨架

替换 PackLayer 的最常见场景是使用私有帧格式或在拆包时注入额外校验:

class MyPackProtocol final : public LLBC_PacketProtocol
{
public:
    // 覆盖 Ctrl 可响应业务下发的控制命令
    bool Ctrl(int cmd, const LLBC_Variant &ctrlData,
              bool &removeSession) override
    {
        LLBC_PrintLn("MyPackProtocol::Ctrl cmd=%d", cmd);
        return false; // 不再向更底层传递
    }
    // Send / Recv 如需完全自定义帧格式,需同时覆盖这两个方法
};

协议栈控制命令(Ctrl)

通过 LLBC_Service::CtrlProtocolStack 向指定会话的协议栈下发自定义命令,命令从最顶层向下依次调用每层的 Ctrl(),直至某层返回 false 截断:

LLBC_Variant ctrlData;
ctrlData["key"] = "value";
svc->CtrlProtocolStack(sessionId, /*cmd=*/10086, ctrlData);

IProtocolFilter 过滤器

LLBC_IProtocolFilter 可以插在任意协议层上,在包经过该层前做拦截判断:

class MyFilter final : public LLBC_IProtocolFilter
{
public:
    // 返回 0 放行,返回 -1 丢弃该包
    int FilterSend(const LLBC_Packet &pkt) override { return 0; }
    int FilterRecv(const LLBC_Packet &pkt) override { return 0; }
    // 返回 0 接受连接,返回 -1 关闭连接
    int FilterConnect(const LLBC_SockAddr_IN &local,
                      const LLBC_SockAddr_IN &peer) override { return 0; }
};

LLBC_IProtocolFilter 目前没有公开的 AddFilter / SetFilter 入口位于 LLBC_Service 接口;过滤器通过 LLBC_ProtocolStack::SetFilter(filter, layer) 安装,而 LLBC_ProtocolStack 由框架内部管理。如需拦截逻辑,通常在自定义协议层的 Send/Recv 内部实现更为直接。

允许未注册 Coder 的原始 payload

默认 NormalProtocolFactory 中,若收到的 opcode 未注册 Coder,CodecProtocol 会报告警告并丢弃该包。要让未注册 Coder 的原始 payload 继续流向 Subscribe 处理函数,应在 Start() 前调用:

svc->SuppressCoderNotFoundWarning();

注册了 AddCoderFactory 的 opcode 不需要此设置;使用 RawProtocolFactory 时也不涉及 CodecProtocol 的 Coder 查找。

线程安全: 协议栈对象随 Session 生命周期存在于 Poller 线程,不应在业务线程直接操作 LLBC_ProtocolStack 成员。控制命令请使用 LLBC_Service::CtrlProtocolStack,它是线程安全的。

参照

下一步