這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在寫伺服器程式時,特別是業務向的服務(比如遊戲伺服器),經常會遇到處理許多用戶端協議的情況,如果是http服務,那麼定義好處理介面,剩下的交給web伺服器就可以了。但是二進位協議就沒有這麼方便了。
通常的自訂二進位協議規則都是固定長度訊息頭+變長訊息體構成,在訊息頭中會有訊息長度,訊息id等欄位。(基於TCP流式協議),伺服器接收到用戶端訊息後,首先讀取訊息頭,解析得到訊息長度,再按照指定長度擷取到完整的訊息體的位元據。
在寫具體商務邏輯時,需要面臨從網路層擷取到的未經處理資料,怎麼映射到記憶體資料結構並調用相應處理介面的問題。前面所說的二進位訊息體的格式多種多樣,大家都有自己的做法,這裡以protobuf為例,構建伺服器端接收到未經處理資料後,通過訊息id映射產生對應的protobuf結構體,並調用處理介面。
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函數裡面新加一行即可,不需要每個協議再單獨處理二進位協議轉換,結構體映射等重複而繁雜的事情。
代碼在: https://github.com/pertgame/gmsg-framework