原文地址-石匠的Blog:http://www.bugclosed.com/post/6git
在寫服務器程序時,特別是業務向的服務(好比遊戲服務器),常常會遇處處理許多客戶端協議的狀況,若是是http服務,那麼定義好處理接口,剩下的交給web服務器就能夠了。可是二進制協議就沒有這麼方便了。github
一般的自定義二進制協議規則都是固定長度消息頭+變長消息體構成,在消息頭中會有消息長度,消息id等字段。(基於TCP流式協議),服務器接收到客戶端消息後,首先讀取消息頭,解析獲得消息長度,再按照指定長度獲取到完整的消息體的二進制數據。golang
在寫具體業務邏輯時,須要面臨從網絡層獲取到的原始數據,怎麼映射到內存數據結構並調用相應處理接口的問題。前面所說的二進制消息體的格式多種多樣,你們都有本身的作法,這裏以protobuf爲例,構建服務器端接收到原始數據後,經過消息id映射生成對應的protobuf結構體,並調用處理接口。web
golang種有一個reflect包,能夠對類型進行反射,動態生成相應結構體,具體作法就是,將protobuf消息結構經過interface類型和消息id註冊到一個自定義map中,在map中保存結構體的類型,具體以下:服務器
type MessageHandler func(msgid uint16, msg interface{}) type MessageInfo struct { msgType reflect.Type msgHandler MessageHandler } var ( msg_map = make(map[uint16]MessageInfo) ) func RegisterMessage(msgid uint16, msg interface{}, handler MessageHandler) { var info MessageInfo info.msgType = reflect.TypeOf(msg.(proto.Message)) info.msgHandler = handler msg_map[msgid] = info }
而後從底層網絡獲取到原始二進制協議數據後,經過消息id在map中找到對應的類型信息並動態建立出結構體類型來解析二進制數據,具體以下:網絡
func HandleRawData(msgid uint16, data []byte) error { if info, ok := msg_map[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") }
這裏利用了reflect的反射機制,動態獲取類型並建立了protobuf結構體,而後經過proto.Umarshal接口解析二進制消息體,最後調用msgHandler進行處理。這裏的msgHandler是一個消息處理接口類型,每一個消息都按照規範定義本身的處理函數,在程序啓動的時候將消息,protobuf結構體和處理函數都統一註冊,以下:數據結構
const ( MsgID_Test1 = iota MsgID_Test2 ) func MessageHandler_Test1(msgid uint16, msg interface{}) { p := msg.(*pb.MsgTest1) fmt.Println("message handler msgid:", msgid, " body:", p) } func MessageHandler_Test2(msgid uint16, msg interface{}) { p := msg.(*pb.MsgTest2) fmt.Println("message handler msgid:", msgid, " body:", p) } func RegistMsg() { RegisterMessage(MsgID_Test1, &pb.MsgTest1{}, MessageHandler_Test1) RegisterMessage(MsgID_Test2, &pb.MsgTest2{}, MessageHandler_Test2) }
此處註冊函數使用的是protobuf消息體的指針類型,因此reflect類型反射的時候,須要經過類型的Elem()函數獲得指針的基類型,再動態建立類型。函數
這樣處理以後,每次新增協議只須要在RegistMsg函數裏面新加一行便可,不須要每一個協議再單獨處理二進制協議轉換,結構體映射等重複而繁雜的事情。post