Service 消息处理与 Handler

LLBC_Service 通过 opcode 路由表将收到的网络包分发给对应的处理函数(Handler)。 业务代码用 Subscribe 把一个成员函数或 delegate 绑定到某个 opcode,框架在网络包 到达并经过协议栈解码后,自动调用该函数并传入 LLBC_Packet &。 PreSubscribe 可在主处理函数之前插入前置过滤逻辑;AddCoderFactory 则让框架 按 opcode 自动实例化并调用 LLBC_Coder::Decode,处理函数直接取解码对象,无需手写 packet >> 散落代码。

Service 消息处理流程

Subscribe:注册 opcode 处理函数

每个 opcode 只能绑定一个主处理函数。最常见形式是绑定到 Component 成员方法:

// 成员方法形式:Subscribe(opcode, obj, &Class::Method)
svc->Subscribe(1, comp, &MyComp::OnRecvData);

// delegate 形式(lambda、自由函数均可)
svc->Subscribe(2, LLBC_Delegate<void(LLBC_Packet &)>([](LLBC_Packet &pkt) {
    // ...
}));

处理函数签名固定为 void(LLBC_Packet &)。

从 Packet 读取数据

LLBC_Packet 提供 >> 运算符(内部调用 Read),按写入顺序逐字段消费 payload。 包头元数据(会话 ID、opcode、状态码)通过 getter 直接读取:

void MyComp::OnRecvData(LLBC_Packet &pkt)
{
    // 读取包头元数据
    int sessionId = pkt.GetSessionId();   // 来源会话 ID
    int opcode    = pkt.GetOpcode();      // opcode(与 Subscribe 注册的一致)
    int status    = pkt.GetStatus();      // 状态码,默认 0

    // 顺序读出 payload 字段
    uint64      playerId;
    LLBC_String playerName;
    int         level;
    pkt >> playerId >> playerName >> level;
}

>> 和 Read() 都沿读游标顺序消费,不能回退或随机定位。若注册了 AddCoderFactory,则框架在回调前已自动调用 Decode,直接用 GetDecoder<T>() 取对象即可,无需再手动 >>(见下一节)。

AddCoderFactory:自动编解码

将 LLBC_CoderFactory 与 opcode 绑定后,框架在收到该 opcode 的包时会自动 分配 LLBC_Coder 实例并调用 Decode;处理函数通过 GetDecoder<T>() 取出已 解码的业务对象:

// 定义 Coder
struct TestData final : public LLBC_Coder
{
    int         iVal;
    LLBC_String strVal;

    bool Encode(LLBC_Packet &pkt) override { pkt << iVal << strVal; return true; }
    bool Decode(LLBC_Packet &pkt) override { pkt >> iVal >> strVal; return true; }
};

// 定义工厂
struct TestDataFactory final : public LLBC_CoderFactory
{
    LLBC_Coder *Create() const override { return new TestData; }
};

// 注册(在 Start() 之前调用)
svc->AddCoderFactory(/*opcode=*/1, new TestDataFactory);
// 或模板形式,框架内部 new:
svc->AddCoderFactory<TestDataFactory>(/*opcode=*/1);

处理函数中直接取解码对象:

void MyComp::OnRecvData(LLBC_Packet &pkt)
{
    TestData *data = pkt.GetDecoder<TestData>();
    LLBC_PrintLn("iVal=%d strVal=%s", data->iVal, data->strVal.c_str());
}

LLBC_Coder 继承自 LLBC_PoolObj,框架在处理完毕后统一回收,业务代码无需 delete。

取回包并发送

在处理函数中构建应答包时,优先从 Service 的线程不安全对象池(Service 线程内) 或线程安全对象池(跨线程)获取 LLBC_Packet,避免频繁堆分配:

void MyComp::OnRecvData(LLBC_Packet &pkt)
{
    TestData *data = pkt.GetDecoder<TestData>();

    // 构造回包:复用收包 sessionId 和 opcode
    TestData *resData  = new TestData;
    resData->iVal  = data->iVal;
    resData->strVal = data->strVal;

    LLBC_Packet *res = GetService()->GetThreadSafeObjPool().Acquire<LLBC_Packet>();
    res->SetHeader(pkt.GetSessionId(), pkt.GetOpcode(), /*status=*/0);
    res->SetEncoder(resData);   // 框架 Send 时调用 Encode,之后自动 delete encoder

    GetService()->Send(res);    // 无论成功与否,res 所有权转移给框架
}

也可以直接通过便捷重载发送 Coder 或原始字节,无需手动构造 Packet:

// 以 Coder 发包(框架内部取 Packet 并调用 Encode)
svc->Send(sessionId, /*opcode=*/1, resData, /*status=*/0);

// 以原始字节发包
svc->Send(sessionId, /*opcode=*/2, buf, len);

使用默认 NormalProtocolFactory 发送原始字节、且该 opcode 未通过 AddCoderFactory 注册 Coder 时,必须在 Start() 前调用 svc->SuppressCoderNotFoundWarning()。否则 CodecProtocol 会丢弃收包, Subscribe 的 Handler 不会被调用。若注册了 CoderFactory,则不需要抑制。

无论 Send 成功与否,LLBC_Packet * 和 LLBC_Coder * 的所有权在调用后 立即归框架管理;调用方绝不能在 Send 后再访问这两个指针。

PreSubscribe:前置过滤

PreSubscribe 在 Subscribe 主处理函数之前执行。返回 false 则中止后续处理流程 (主处理函数不会被调用):

// 注册前置过滤(与 Subscribe 同一 opcode)
svc->PreSubscribe(1, comp, &MyComp::OnPreRecvData);

// 实现:返回 false 可丢弃该包
bool MyComp::OnPreRecvData(LLBC_Packet &pkt)
{
    TestData *data = pkt.GetDecoder<TestData>();
    if (data->iVal < 0)
        return false;  // 非法数据,中止处理

    // 将预处理结果挂到 pkt,主处理函数通过 GetPreHandleResult<T>() 取用
    pkt.SetPreHandleResult(LLBC_Malloc(char, 64), [](void *p){ free(p); });
    return true;
}

若编译时开启了 LLBC_CFG_COMM_ENABLE_UNIFY_PRESUBSCRIBE,还可以调用 UnifyPreSubscribe 注册全局前置过滤,对所有 opcode 生效,签名相同: bool(LLBC_Packet &)。

状态码处理

若编译时开启了 LLBC_CFG_COMM_ENABLE_STATUS_HANDLER,可为 (opcode, status) 组合注册单独的处理函数;注册后框架不再调用该 opcode 的默认主处理函数:

// 注册状态码处理函数
svc->SubscribeStatus(/*opcode=*/1, /*status=*/1, comp, &MyComp::OnStatus_1);

void MyComp::OnStatus_1(LLBC_Packet &pkt)
{
    LLBC_PrintLn("opcode=%d status=%d", pkt.GetOpcode(), pkt.GetStatus());
}

会话事件通知

会话建立 / 断开等非消息事件不走 Subscribe 路由,而是通过 LLBC_Component::OnEvent 回调分发。组件内部按 eventType 分支处理:

void MyComp::OnEvent(int eventType, const LLBC_Variant &eventParams) override
{
    switch (eventType)
    {
        case LLBC_ComponentEventType::SessionCreate:
        {
            auto *info = eventParams.As<LLBC_SessionInfo *>();
            LLBC_PrintLn("Session created: %s", info->ToString().c_str());
            break;
        }
        case LLBC_ComponentEventType::SessionDestroy:
        {
            auto *info = eventParams.As<LLBC_SessionDestroyInfo *>();
            LLBC_PrintLn("Session destroyed: %s", info->ToString().c_str());
            break;
        }
        default:
            break;
    }
}

业务逻辑也可以用 AddComponentEvent 向组件投递自定义事件:

// eventType 值从 LLBC_ComponentEventType::LogicBegin 开始定义
GetService()->AddComponentEvent(MyCompEv1, LLBC_Variant("hello"));

详细事件类型定义参见 组件生命周期与事件。

参照

下一步