18

Golang 游戏leaf系列(四) protobuf数据怎么处理

 5 years ago
source link: https://www.tuicool.com/articles/fER7Zjm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Golang 游戏leaf系列(三) NewAgent在chanrpc和skeleton中怎么通讯 中(下文简称系列三),说了如何处理NewAgent:

skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)

其实在game模块的handler.go中,同样注册了如何处理网络消息:

func init() {
    // 向当前模块(game 模块)注册 消息处理函数
    handler(&msg.Test{}, handleTest)
    handler(&msg.UserLogin{}, handleUserLogin)
    handler(&msg.UserRegister{}, handleUserRegister)
}

func handler(m interface{}, h interface{}) {
    skeleton.RegisterChanRPC(reflect.TypeOf(m), h)
}

func handleTest(args []interface{}) {
    // 收到的 Test 消息
    m := args[0].(*msg.Test)
    // 消息的发送者
    a := args[1].(gate.Agent)

    // 输出收到的消息的内容
    log.Debug("hello %v", m.GetTest())

    retBuf := &msg.Test{
        Test: *proto.String("client"),
    }
    // 给发送者回应一个 Test 消息
    a.WriteMsg(retBuf)
}

也就是说,他们和内部模块通讯方式是一样的,也是用chanrpc。当然,我们还要做一些准备工作

一、流程简述

1.msg模块

// 使用 Protobuf 消息处理器
var Processor = protobuf.NewProcessor()

func init() {
    id1 := Processor.Register(&Test{})
    id2 := Processor.Register(&UserLogin{})
    id3 := Processor.Register(&UserRegister{})
    id4 := Processor.Register(&UserResult{})
    id5 := Processor.Register(&UserST{})
}

指定Processor并注册

2.在gate的router.go中设置路由

func init() {
    // 这里指定消息 路由到 game 模块
    msg.Processor.SetRouter(&msg.Test{}, game.ChanRPC)
    msg.Processor.SetRouter(&msg.UserLogin{}, game.ChanRPC)
    msg.Processor.SetRouter(&msg.UserRegister{}, game.ChanRPC)
}

3.简述

可以看到,三部分使用的都是 &msg.Test{} 这样的东西作为一个类似key的东西在互相沟通。

// -------------------------
// | id | protobuf message |
// -------------------------
type Processor struct {
    littleEndian bool
    msgInfo      []*MsgInfo
    msgID        map[reflect.Type]uint16
}

type MsgInfo struct {
    msgType       reflect.Type
    msgRouter     *chanrpc.Server
    msgHandler    MsgHandler
    msgRawHandler MsgHandler
}

type MsgHandler func([]interface{})

func (p *Processor) SetRouter(msg proto.Message, msgRouter *chanrpc.Server) {
    msgType := reflect.TypeOf(msg)
    id, ok := p.msgID[msgType]
    if !ok {
        log.Fatal("message %s not registered", msgType)
    }

    p.msgInfo[id].msgRouter = msgRouter
}

可以看到,路由的东西,确实是以 msgType := reflect.TypeOf(msg) 这种key存到了一个map里。这个map叫msgID,它的Value就是msgId。然后这些ID是从0开始的,作为访问切片msgInfo的索引。

关于msgId从0开始,在系列一讲过:

//leaf/network/protobuf.go的Register方法
    i := new(MsgInfo)
    i.msgType = msgType
    p.msgInfo = append(p.msgInfo, i)
    id := uint16(len(p.msgInfo) - 1)
    p.msgID[msgType] = id
    return id

关于使用 msgType := reflect.TypeOf(msg) 得到了一个 reflect.Type 作为key,会不会重复呢,因为msg是由自己定义的proto生成的结构,当然不会重复。但是为什么不用这个msg作为key,而是要用它的反射值呢,我没搞懂。关于反射,可以参考 Golang 学习笔记十四 反射

现在说一下执行顺序:

Processor.Register
skeleton.RegisterChanRPC
msg.Processor.SetRouter(&msg.Test{}, game.ChanRPC)

具体是怎么转交的,可以看一下protobuf的Router方法

func (p *Processor) Route(msg interface{}, userData interface{}) error {
    // raw
    if msgRaw, ok := msg.(MsgRaw); ok {
        if msgRaw.msgID >= uint16(len(p.msgInfo)) {
            return fmt.Errorf("message id %v not registered", msgRaw.msgID)
        }
        i := p.msgInfo[msgRaw.msgID]
        if i.msgRawHandler != nil {
            i.msgRawHandler([]interface{}{msgRaw.msgID, msgRaw.msgRawData, userData})
        }
        return nil
    }

    // protobuf
    msgType := reflect.TypeOf(msg)
    id, ok := p.msgID[msgType]
    if !ok {
        return fmt.Errorf("message %s not registered", msgType)
    }
    i := p.msgInfo[id]
    if i.msgHandler != nil {
        i.msgHandler([]interface{}{msg, userData})
    }
    if i.msgRouter != nil {
        i.msgRouter.Go(msgType, msg, userData)
    }
    return nil
}

可以看到是用 .Go(msgType, msg, userData) 这种方式。那么Route是什么时候调用的,这个在系列二看到过,大概就是收到消息时执行Run方法

func (a *agent) Run() {
    for {
        data, err := a.conn.ReadMsg()
        if err != nil {
            log.Debug("read message: %v", err)
            break
        }

        if a.gate.Processor != nil {
            msg, err := a.gate.Processor.Unmarshal(data)
            if err != nil {
                log.Debug("unmarshal message error: %v", err)
                break
            }
            err = a.gate.Processor.Route(msg, a)
            if err != nil {
                log.Debug("route message error: %v", err)
                break
            }
        }
    }
}

可以看到,Run中执行Route传入了两个参数,第一个是数据本身,第二个是agent。然后 .Go(msgType, msg, userData) 相当于把这两个参数都抛出去了。那么也可以知道我们在handler.go中的Function会如何处理:

func handleTest(args []interface{}) {
    // 收到的 Test 消息
    m := args[0].(*msg.Test)
    // 消息的发送者
    a := args[1].(gate.Agent)
...

第一个正是msg,第二个是Agent接口,因为agent没暴露,agent实现了Agent接口,所以暴露的是Agent接口。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK