Leaf ChanRPC

由于 Leaf 中,每个模块跑在独立的 goroutine 上,为了模块间方便的相互调用就有了基于 channel 的 RPC 机制。一个 ChanRPC 需要在游戏服务器初始化的时候进行注册(注册过程不是 goroutine 安全的),例如 LeafServer 中 game 模块注册了 NewAgent 和 CloseAgent 两个 ChanRPC:

  1. package internal
  2.  
  3. import (
  4. "github.com/name5566/leaf/gate"
  5. )
  6.  
  7. func init() {
  8. skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)
  9. skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)
  10. }
  11.  
  12. func rpcNewAgent(args []interface{}) {
  13.  
  14. }
  15.  
  16. func rpcCloseAgent(args []interface{}) {
  17.  
  18. }

使用 skeleton 来注册 ChanRPC。RegisterChanRPC 的第一个参数是 ChanRPC 的名字,第二个参数是 ChanRPC 的实现。这里的 NewAgent 和 CloseAgent 会被 LeafServer 的 gate 模块在连接建立和连接中断时调用。ChanRPC 的调用方有 3 种调用模式:

  • 同步模式,调用并等待 ChanRPC 返回
  • 异步模式,调用并提供回调函数,回调函数会在 ChanRPC 返回后被调用
  • Go 模式,调用并立即返回,忽略任何返回值和错误gate 模块这样调用 game 模块的 NewAgent ChanRPC(这仅仅是一个示例,实际的代码细节复杂的多):
  1. game.ChanRPC.Go("NewAgent", a)

这里调用 NewAgent 并传递参数 a,我们在 rpcNewAgent 的参数 args[0] 中可以取到 a(args[1] 表示第二个参数,以此类推)。

更加详细的用法可以参考 leaf/chanrpc。需要注意的是,无论封装多么精巧,跨 goroutine 的调用总不能像直接的函数调用那样简单直接,因此除非必要我们不要构建太多的模块,模块间不要太频繁的交互。模块在 Leaf 中被设计出来最主要是用于划分功能而非利用多核,Leaf 认为在模块内按需使用 goroutine 才是多核利用率问题的解决之道。