在Leaf中使用Protobuf

技术文档网 2021-06-01

本文假定你已经仔细阅读过 TUTORIAL

一个简单的上手

在开始本范例之前,请确保已经获取了 LeafLeafserver 并且正确配置(否则请阅读 TUTORIAL 中相关内容)。

编写服务器端

编辑 Leafserver 中的文件 msg/msg.go

package msg

import (
    "github.com/name5566/leaf/network/protobuf"
)

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

func init() {

}

我们在 msg.go 相同目录下创建一个 test.proto 文件,内容如下:

package msg;

message Hello {
    required string name = 1;
}

这里定义了一个 Hello 消息。编译 test.proto 文件(对此不了解?请先阅读《在 Golang 中使用 Protobuf》一文)得到 test.pb.go 文件(假定在 test.proto 当前目录下执行以下命令):

protoc --go_out=. test.proto

现在开始注册我们的 Hello 消息。编辑 msg.go 文件,修改 init 函数:

func init() {
    // 这里我们注册了消息 Hello
    Processor.Register(&Hello{})
}

接下来处理 Hello 消息的路由。这里,我们将 Hello 消息路由到 game 模块中。打开 LeafServer gate/router.go,敲入如下代码:

package gate

import (
    "server/game"
    "server/msg"
)

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

一切就绪,我们现在可以在 game 模块中处理 Hello 消息了。打开 LeafServer game/internal/handler.go,敲入如下代码:

package internal

import (
    "github.com/golang/protobuf/proto"
    "github.com/name5566/leaf/gate"
    "github.com/name5566/leaf/log"
    "reflect"
    "server/msg"
)

func init() {
    // 向当前模块(game 模块)注册 Hello 消息的消息处理函数 handleHello
    handler(&msg.Hello{}, handleHello)
}

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

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

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

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

到这里,一个简单的范例就完成了。

客户端如何对接服务器

在 Leaf 中,默认的 Protobuf Processor 将一个完整的 Protobuf 消息定义为如下格式:

-------------------------
| id | protobuf message |
-------------------------

其中 id 为 2 个字节。如果你选择使用 TCP 协议时,在网络中传输的消息格式如下:

-------------------------------
| len | id | protobuf message |
-------------------------------

如果你选择使用 WebSocket 协议时,发送的消息格式如下:

-------------------------
| id | protobuf message |
-------------------------

其中 len 默认为两个字节,len 和 id 默认使用网络字节序。客户端需要按此格式进行编码。

常见问题

Protobuf id 的生成规则是什么?

首先,Protobuf id 是 Leaf 的 Protobuf Processor 自动生成的。生成规则是从 0 开始,第一个注册的消息 ID 为 0,第二个注册的消息 ID 为 1,以此类推。例如:

func init() {
    Processor.Register(&MsgA{}) // 消息 ID 为 0
    Processor.Register(&MsgB{}) // 消息 ID 为 1
    Processor.Register(&MsgC{}) // 消息 ID 为 2
}

客户端如何正确处理消息 ID?

由于消息 ID 是服务器生成的,因此建议服务器导出消息 ID 给客户端使用。这里提供一个简单的思路,假定客户端使用 Lua,这时候服务器可以导出消息 ID 为一个 Lua 源文件,此源文件中包含一个 table,可能内容如下:

msg = {
    [0] = "msg.MsgA",
    [1] = "msg.MsgB",
    [2] = "msg.MsgC",
}

Lua 客户端加载此 Lua 源文件,得到 msg table,然后就可以从消息 ID(网络中过来的消息)获取到消息了。当然,各位同学需要按照自己的实际情况来做实际的处理。

为了导出消息 ID 给客户端,Leaf 的 Protobuf Processor 提供了方法:

func (p *Processor) Range(f func(id uint16, t reflect.Type))

通过此方法,我们可以遍历所有 Protobuf 消息,然后(比如说按上述方式)导出消息 ID 给客户端使用。

为什么客户端解析出来的 id 很大?

一般来说,id 很大是因为字节序问题。Leafserver 默认使用大端序,这个设定可以配置,具体修改 Leafserver 中 conf/conf.go 中 LittleEndian 配置,true 表示使用小端序,false 表示使用大端序。

相关文章

  1. Golang中匿名函数的使用

    匿名函数在函数中抽象出来 对于一个项目,为了抽象,可以将一些功能相同的操作写成匿名,下面就是展示如何使用匿名函数: 匿名函数的价值: 在函数内部的匿名函数,共享函数的局部变量,其实这就是封装,类似于

  2. Golang中client-server模型

    简易的client-server模型 客户端和服务端的交互过程 服务端 (1)监听端口返回listen: net.Listen() (2)同意连接,得到conn: listen.Accept() (

  3. 在Leaf中使用Protobuf

    本文假定你已经仔细阅读过 TUTORIAL 一个简单的上手 在开始本范例之前,请确保已经获取了 Leaf 和 Leafserver 并且正确配置(否则请阅读 TUTORIAL 中相关内容)。 编写服务

随机推荐

  1. Golang中匿名函数的使用

    匿名函数在函数中抽象出来 对于一个项目,为了抽象,可以将一些功能相同的操作写成匿名,下面就是展示如何使用匿名函数: 匿名函数的价值: 在函数内部的匿名函数,共享函数的局部变量,其实这就是封装,类似于

  2. Golang中client-server模型

    简易的client-server模型 客户端和服务端的交互过程 服务端 (1)监听端口返回listen: net.Listen() (2)同意连接,得到conn: listen.Accept() (

  3. 在Leaf中使用Protobuf

    本文假定你已经仔细阅读过 TUTORIAL 一个简单的上手 在开始本范例之前,请确保已经获取了 Leaf 和 Leafserver 并且正确配置(否则请阅读 TUTORIAL 中相关内容)。 编写服务