1. 协议格式
任何游戏在动手编写代码前服务器与客户端的开发人员的首要任务是商定好协议的规则格式。
下面以sibo协议为例:
Header [8]byte
payloadLength [4]byte
payload [payloadLength]byte
---
---
Header各个byte的定义:
1 1 1 1 1 3
magicNumber|version|ConsistentDefine|serializationType|moduleID|messageID
---
1 1 1 111 11
消息类型 是否是心跳包 是否是单向通信 压缩类型 消息状态
---
magicNumber: 协议Header的起始标识
version: 协议版本号
serializationType: 序列化类型(json,bson,protobuf,msgpack)
moduleID: 模块id
messageID: 模块下的消息id
moduleID与messageID组合成为一个msg的唯一id
2. 协议编码解码器实现
根据定好的协议编写对应编码解码器,sibo协议的编解码器源码如下:
package protocol
import (
"errors"
"encoding/binary"
"io"
)
var MaxMessageLength = 0
const (
magicNumber byte = 0x09
)
var (
ErrMessageToLong = errors.New("message is too long")
)
type MessageType byte
const (
Request MessageType = iota
Response
)
type MessageStatusType byte
const (
Normal MessageStatusType = iota
Error
)
type CompressType byte
const (
None CompressType = iota
Gzip
Zlib
)
type SerializeType byte
const (
SerializeNone SerializeType = iota
JSON
ProtoBuffer
MsgPack
Bson
)
type Message struct {
*Header
Payload []byte
data []byte
}
func NewMessage() *Message {
header := Header([8]byte{})
header[0] = magicNumber
return &Message{
Header: &header,
}
}
type Header [8]byte
func (h Header) CheckMagicNumber() bool {
return h[0] == magicNumber
}
func (h Header) Version() byte {
return h[1]
}
func (h *Header) SetVersion(version byte) {
h[1] = version
}
func (h Header) MessageType() MessageType {
return MessageType(h[2]&0x80) >> 7
}
func (h *Header) SetMessageType(messageType MessageType) {
h[2] = h[2] | (byte(messageType) << 7)
}
func (h Header) IsHeartbeat() bool {
return h[2]&0x40 == 0x40
}
func (h *Header) SetHeartbeat(hb bool) {
if hb {
h[2] = h[2] | 0x40
} else {
h[2] = h[2] & 0xbf
}
}
func (h Header) IsOneway() bool {
return h[2]&0x20 == 0x20
}
func (h *Header) SetOneway(oneway bool) {
if oneway {
h[2] = h[2] | 0x20
} else {
h[2] = h[2] &^ 0x20
}
}
func (h Header) CompressType() CompressType {
return CompressType((h[2] & 0x1C) >> 2)
}
func (h *Header) SetCompressType(ct CompressType) {
h[2] = h[2] | ((byte(ct) << 2) & 0x1C)
}
func (h Header) MessageStatusType() MessageStatusType {
return MessageStatusType(h[2] & 0x03)
}
func (h *Header) SetMessageStatusType(mt MessageStatusType) {
h[2] = h[2] | (byte(mt) & 0x03)
}
func (h Header) SerializeType() SerializeType {
return SerializeType((h[3] & 0xF0) >> 4)
}
func (h *Header) SetSerializeType(st SerializeType) {
h[3] = h[3] | (byte(st) << 4)
}
func (h Header) ModuleMessageID() uint32 {
return binary.BigEndian.Uint32(h[4:8])
}
func (h Header) Module() byte {
return h[4]
}
func (h *Header) SetModule(m byte) {
h[4] = m
}
func (h Header) MessageID() uint32 {
msgId := binary.BigEndian.Uint32(h[4:8])
return (msgId << 8) >> 8
}
func (h *Header) SetMessageID(msgId uint32) {
realMsgId := (uint32(h[4]) << 24) | ((msgId << 8) >> 8)
binary.BigEndian.PutUint32(h[4:8], realMsgId)
}
func (m Message) Clone() *Message {
header := *m.Header
c := GetPooledMsg()
c.Header = &header
return c
}
func (m *Message) Reset() {
resetHeader(m.Header)
m.Payload = m.Payload[:0]
m.data = m.data[:0]
}
var zeroHeaderArray Header
var zeroHeader = zeroHeaderArray[1:]
func resetHeader(h *Header) {
copy(h[1:], zeroHeader)
}
func (m Message) Encode() []byte {
l := 12 + len(m.Payload)
data := make([]byte, l)
copy(data, m.Header[:])
binary.BigEndian.PutUint32(data[8:12], uint32(len(m.Payload)))
copy(data[12:], m.Payload)
return data
}
func (m Message) WriteTo(w io.Writer) error {
_, err := w.Write(m.Header[:])
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, uint32(len(m.Payload)))
if err != nil {
return err
}
_, err = w.Write(m.Payload)
return err
}
func (m *Message) Decode(r io.Reader) error {
_, err := io.ReadFull(r, m.Header[:])
if err != nil {
return err
}
lenData := poolUint32Data.Get().(*[]byte)
_, err = io.ReadFull(r, *lenData)
if err != nil {
poolUint32Data.Put(lenData)
return err
}
l := binary.BigEndian.Uint32(*lenData)
poolUint32Data.Put(lenData)
if MaxMessageLength > 0 && int(l) > MaxMessageLength {
return ErrMessageToLong
}
data := make([]byte, int(l))
_, err = io.ReadFull(r, data)
if err != nil {
return err
}
m.data = data
m.Payload = data[:]
return err
}
func Read(r io.Reader) (*Message, error) {
msg := NewMessage()
err := msg.Decode(r)
if err != nil {
return nil, err
}
return msg, nil
}
3. 消息的复用
需要注意的是上面的代码中的Clone方法,在实际场景中每个message的结构都是一样的只是具体的内容值不同,
而且每个message的header是大小结构是相同的,因此我们可以将每个message的header缓存起来复用。具体实现如下:
import "sync"
var msgPool = sync.Pool{
New: func() interface{} {
header := Header([8]byte{})
header[0] = magicNumber
return &Message{Header: &header}
},
}
func GetPooledMsg() *Message {
return msgPool.Get().(*Message)
}
func FreeMsg(msg *Message) {
msg.Reset()
msgPool.Put(msg)
}
var poolUint32Data = sync.Pool{
New: func() interface{} {
data := make([]byte, 4)
return &data
},
}
需要注意的是上面代码使用sync.Pool做为缓存池,sync.Pool中的缓存的生命周期是在两次垃圾回收之间。
4. 总结
以上即是sibo协议的定制和解析,实际开发中可自由定制自己的协议。
协议可以随意定义但定义好的协议需要比较困难,需要结合自己的游戏以及考虑协议的拓展性、通用性等。
一旦协议定好后,协议的编解码器编写起来可谓手到渠成。
源码地址 https://github.com/shuhonglin/sibo