1. 4.2 会话管理(Session)

4.1 把 Gate 定位为路由枢纽;本节拆开 SessionManager(业务侧 authId → 分片)BaseServer(authId → IChannel 的职责边界。类型与注释以 zhenyi/zgate/session.gozhenyi-baseSetChannelAuth / GetChannelByAuthId 为准。

1.1. 4.2.1 会话(Session)是什么?

在 zhenyi 中,Session 不是传统意义上的"会话对象",而是一组映射关系,用来回答两个问题:

  1. 这个用户在哪个连接上?(authId → Channel)
  2. 这个用户被分配到了哪些业务 Actor?(authId → actorType → actorId)

为什么要维护这些映射?因为网关收到消息后需要路由,路由需要知道目标在哪。

1.2. 4.2.2 两层映射

zhenyi 的 Session 实际上分两层管理:

1.2.1. 第一层:authId → Channel(网络层)

BaseServer 维护,在连接级别:

// BaseServer 中的映射(实现为 zcoll.SyncMap,示意)
// authId → IChannel

// 绑定:登录成功后调用(Gate 侧常用 zgate.Server.SetSessionAuth 包装)
func (b *BaseServer) SetChannelAuth(channelId uint64, authId uint64) {
    channel := b.GetChannel(channelId)
    if channel == nil {
        return
    }
    channel.SetAuthId(authId)
    b.authChannels.Store(authId, channel)
}

// 查询:通过 authId 找连接
func (b *BaseServer) GetChannelByAuthId(authId uint64) IChannel {
    ch, ok := b.authChannels.Load(authId)
    if !ok {
        return nil
    }
    channel, ok1 := ch.(IChannel)
    if !ok1 {
        return nil
    }
    return channel
}

绑定时机: 业务 Actor 处理完登录逻辑后,调用 Gate 提供的方法绑定。

解绑时机: 连接断开时自动清理。

1.2.2. 第二层:authId → Actor(业务层)

SessionManager 维护,在业务级别:

// SessionManager 中的映射
info map[int64]map[int32]int32  // authId → actorType → actorId

// 绑定:用户被分配到某个业务 Actor 后(info 为空时返回错误)
func (m *SessionManager) SetSessionActorId(authId int64, info map[int32]int32) error {
    // info: {ActorType_IM: 2, ActorType_Match: 3}
    if len(info) == 0 {
        return errors.New("not actor")
    }
    if _, ok := m.info[authId]; !ok {
        m.info[authId] = make(map[int32]int32)
    }
    for k, v := range info {
        m.info[authId][k] = v
    }
    return nil
}

// 查询:知道用户在哪个 IM Actor 上(未找到返回 0)
func (m *SessionManager) GetSessionActorId(authId int64, actorType int32) int32 {
    info1, ok := m.info[authId]
    if !ok {
        return 0
    }
    if actorId, ok1 := info1[actorType]; ok1 {
        return actorId
    }
    return 0
}

// 清理:用户下线
func (m *SessionManager) DelSessionActorId(authId int64) {
    delete(m.info, authId)
}

绑定时机: 由业务 Actor 决定,框架不强制。

1.2.3. 为什么分两层?

管理什么 谁维护
网络层 authId → Channel BaseServer(自动)
业务层 authId → ActorType → ActorId SessionManager(业务驱动)

网络层解决的是"消息发回给哪个连接",业务层解决的是"消息转发给哪个 Actor"。

两个问题不一样:

  • 给用户发消息 → 需要知道 Channel(通过 authId 查 BaseServer)
  • 查用户的业务状态 → 需要知道 ActorId(通过 authId 查 SessionManager)

1.3. 4.2.3 线程安全:不需要锁

两层映射都在 Gate Actor 的主线程中访问:

// SessionManager 的注释
// 线程安全约定:所有方法仅在 GateServer Actor 的 Run goroutine 中调用(单线程),
// 通过 Actor mailbox 消息驱动,因此无需加锁。

为什么不需要锁?因为 Gate Actor 的消息是串行处理的(第三章讲过的 Mailbox 机制),同一时间只有一个 goroutine 在读写这些 map。

但如果从 AsyncRun 的协程池中直接访问 SessionManager,就会产生 data race。所以代码注释明确说了:禁止从 AsyncRun/Worker goroutine 直接调用

需要访问的话,应通过 safeUpdate / 向 Gate 发消息 等路径回到邮箱线程(见 3.4)。

1.4. 4.2.4 用户上线到下线的完整流程

1. 客户端连接 → OnAccept
   → channelId 分配(自动递增)
   → authId = 0(未认证)
   → 进入在线列表

2. 客户端发送登录请求 → Gate 路由到 IM Actor
   → IM Actor 验证用户身份

3. 登录成功 → 经 Gate API 绑定(示例)
   → `Server.SetSessionAuth(channelId, authId)` → 底层 `SetChannelAuth`(网络层)
   → `SetSessionActorId(authId, ...)`(业务层,`int64` / `int32` 以 `session.go` 为准)
   → 此时 authId > 0,用户已认证

4. 正常通信
   → 其他 Actor 通过 userId 找到 Channel 发消息
   → Gate 通过 userId 找到 ActorId 路由消息

5. 连接断开 → OnClose
   → authChannels.Delete(authId)  (自动清理网络层映射)
   → 业务 Actor 清理 SessionManager  (需要业务层自己处理)

注意第 5 步:网络层的映射是自动清理的,但业务层的映射需要业务 Actor 自己清理。这是因为框架不知道业务层什么时候该清理——用户可能断线重连,中间状态需要业务自己判断。

1.5. 4.2.5 一个用户多条连接?

当前的 SessionManager 设计是 authId → actorId(一对多:一个用户可能在多个 Actor 上),但 authId → Channel(一对一:一个 authId 只对应一个 Channel)。

这意味着:

  • 同一个用户不能同时有两条连接(新的绑定会覆盖旧的)
  • 但同一个用户可以同时被多个业务 Actor 服务(IM + Match + Chat)

如果业务需要支持多设备登录(手机 + PC 同时在线),需要自己在业务层扩展,比如 authId → deviceId → Channel

1.6. 4.2.6 本节要点

  1. SessionManagerauthId(int64) → actorType(int32) → actorId(int32),供本地/远程路由查分片。
  2. BaseServerauthId → IChannel,供下行 GetChannelByAuthId 等路径。
  3. 线程安全:仅假设在 Gate Actor 邮箱线程调用;禁止 worker 直连。
  4. 生命周期:断线清 Channel 侧绑定;SessionManager 条目须业务在合适时机 DelSessionActorId
  5. 多连接:当前 authId ↔ Channel 为常用的一对一模型;多设备需业务扩展映射。

4.3 节:本地表、远程候选与 PickOne 策略。

results matching ""

    No results matching ""