一种golang二进制协议处理接口映射方式
简介
在写服务器程序时,特别是业务向的服务(比如游戏服务器),经常会遇到处理许多客户端协议的情况,如果是http服务,那么定义好处理接口,剩下的交给web服务器就可以了。但是二进制协议就没有这么方便了。
通常的自定义二进制协议规则都是固定长度消息头+变长消息体构成,在消息头中会有消息长度,消息id等字段。(基于TCP流式协议),服务器接收到客户端消息后,首先读取消息头,解析得到消息长度,再按照指定长度获取到完整的消息体的二进制数据。
在写具体业务逻辑时,需要面临从网络层获取到的原始数据,怎么映射到内存数据结构并调用相应处理接口的问题。前面所说的二进制消息体的格式多种多样,大家都有自己的做法,这里以protobuf为例,构建服务器端接收到原始数据后,通过消息id映射生成对应的protobuf结构体,并调用处理接口。
golang种有一个reflect包,可以对类型进行反射,动态生成相应结构体,具体做法就是,将protobuf消息结构通过interface类型和消息id注册到一个自定义map中,在map中保存结构体的类型。
具体实现
网关层
- 主要负责接受网络数据,然后将数据进行解析,分发到各个业务处理模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package gate
import (
"errors"
"reflect"
"github.com/golang/protobuf/proto"
)
type MessageHandler func(msgID uint16, msg interface{})
type MessageInfo struct {
msgType reflect.Type
msgHandler MessageHandler
}
type Gate struct {
msgHandlerMap map[uint16]MessageInfo
}
func NewGate() *Gate {
return &Gate{
msgHandlerMap: make(map[uint16]MessageInfo),
}
}
func (g *Gate) RegisterMessage(msgID uint16, msg interface{}, handler MessageHandler) {
var info MessageInfo
info.msgType = reflect.TypeOf(msg.(proto.Message))
info.msgHandler = handler
g.msgHandlerMap[msgID] = info
}
func (g *Gate) HandleRawData(msgID uint16, data []byte) error {
if info, ok := g.msgHandlerMap[msgID]; ok {
msg := reflect.New(info.msgType.Elem()).Interface()
err := proto.Unmarshal(data, msg.(proto.Message))
if err != nil {
return err
}
info.msgHandler(msgID, msg)
return err
}
return errors.New("not found msgID")
}
模块A
- 业务逻辑模块需要向网关注册各个协议
Register
方法向网关注册协议- 导入的
"test/protocol"
为proto
编译之后的go
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package module1
import (
"fmt"
"test/gate"
pb "test/protocol"
)
func Register(g *gate.Gate) {
g.RegisterMessage(1, &pb.Player{}, PlayerHandler)
}
func PlayerHandler(msgID uint16, msg interface{}) {
p := msg.(*pb.Player)
fmt.Println("player handler msgid:", msgID, " body:", p)
}
主函数
主函数工作流程为
- 初始化网关
- 各个模块向网关注册服务
- 启动服务
本例只是一个测试,因此我给网关
gate
增加了一个HandleRawData
的方法,模拟接受到网络数据之后的流程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32package main
import (
"github.com/golang/protobuf/proto"
"test/gate"
"test/module1"
"test/module2"
pb "test/protocol"
)
func main() {
g := gate.NewGate()
module1.Register(g)
module2.Register(g)
// test data 1
player := &pb.Player{
OpenId: "openid",
Nickname: "nickname",
Sex: 1,
}
data, _ := proto.Marshal(player)
g.HandleRawData(1, data)
// test data 2
time := &pb.Time{
EventTime: "xxxxxx",
}
data, _ = proto.Marshal(time)
g.HandleRawData(2, data)
}