This is a creation in Article, where the information may have evolved or changed.
When writing server programs, especially business-to-service (such as game servers), often encounter many client protocols, if the HTTP service, then define the processing interface, the rest to the Web server. But the binary protocol is not so convenient.
The usual custom binary protocol rules are fixed-length message headers + variable length message bodies, with fields such as message length, message ID, and so on in the message header. (based on TCP streaming protocol), after the server receives the client message, it first reads the message header, resolves the message length, and obtains the binary data of the complete message body by the specified length.
When writing specific business logic, you need to face the raw data obtained from the network layer, how to map to the memory data structure and invoke the corresponding processing interface problem. The previous binary message body format is various, we all have their own approach, here in Protobuf as an example, the building server side received the original data, through the message ID map to generate the corresponding PROTOBUF structure, and call the processing interface.
Golang has a reflect package that can reflect the type and dynamically generate the corresponding structure by registering the PROTOBUF message structure with the interface type and message ID into a custom map, preserving the type of struct in the map, as follows:
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}
After acquiring the original binary protocol data from the underlying network, the corresponding type information is found in the map by the message ID and the structure type is dynamically created to parse the binary data, as follows:
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")}
This takes advantage of the reflect reflection mechanism, dynamically acquires the type and creates the PROTOBUF structure, and then passes the Proto. The Umarshal interface parses the binary message body and finally calls Msghandler for processing. Here Msghandler is a message processing interface type, each message is defined by the specification of its own processing function, at the time of program startup, the message, PROTOBUF structure and processing functions are unified registration, as follows:
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)}
The registered function here uses the pointer type of the PROTOBUF message body, so when the reflect type is reflected, it is necessary to get the base type of the pointer through the type's Elem () function, and then dynamically create the type.
After this processing, each new protocol only need to add a new line in the REGISTMSG function, do not need each protocol to deal with the binary protocol conversion, structure mapping and other repetitive and complex things.
Code in: Https://github.com/pertgame/gmsg-framework