golang中使用訊息名稱建立protobuf訊息

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

golang 中根據 protobuf message name 動態執行個體化 protobuf 訊息,訊息內容通過輸入 json 檔案指定

 

背景:

    項目中使用 protobuf 作為 rpc 調用協議,計劃用 golang 實現一個壓測工具,希望能夠指定 message name 和 json 動態構建 protobuf 訊息;從 json 解析到 golang protobuf message 可以用 jsonpb,這裡使用 UnmarshalString,函數簽名

func UnmarshalString(str string, pb proto.Message) error

str 是 json 字串,message 是自訂的 proto messgae 介面。於是剩下需要做的是通過 message name 擷取對應的 proto.Message 介面,搜尋了一下,對於 golang 沒有找到很好的方法,檢查了 protoc 產生的 golang 訊息檔案,可以按以下方式根據 message name 擷取到 message type,然後利用 golang reflect 包執行個體畫訊息;

 

解決方式:

    簡單來說,就是需要根據 message name 擷取到 message type, 然後利用 golang 反射執行個體化訊息結構。從 message name 擷取 message type,最直觀的是維護一個 map[string]reflect.Type 的字典,protoc 產生的 golang 代碼已經包含這個字典,自訂的 message 會通過 proto.RegisterType 註冊到 protoTypes 和 revProtoTypes 這兩個結構中,並提供 MessageName 和 MessageType 用來通過 name 擷取 type 或者反之, 相關代碼在 proto/properties.go 中, 由此可以實現通過 message name 擷取到 message type 進而執行個體化訊息的功能。

   其它包括 enum 類型,extensions 都有相應的註冊/擷取函數 proto.RegisterEnum, proto.RegisterExtension;

 

樣本:

    以下以一個 rpc 訊息定義為例實現從訊息名稱執行個體化一個訊息執行個體,完整代碼見  https://github.com/wuyidong/parse_pb_by_name_golang

    以下一個簡單 protobuf 做 rpc 協議的簡單例子,我們在 package rpc 中定義了協議的一般格式,由協議頭(Head)和訊息本身(Body)組成,Body 全部為可選欄位,用於填充具體的協議,Head 為固定格式, 其中 Head.message_type 用於標識 Body 所帶有的協議類型,服務端根據 Head.message_type 路由到具體的處理過程,具體的協議如 CreateAccountRequest/CreateAccountResponse 等都作為 rpc.Body 的可選欄位。

    rpc.proto -->  rpc 訊息格式

package rpc;message RPCMessage  {    // 訊息頭部    required Head head = 1;    // 訊息內容    required Body body = 2;};message Head {    // 請求 uuid    required string session_no = 1;    // 請求訊息類型    required int32 message_type = 2;};message Body {    extensions 1000 to max;};message ResponseCode {    required int32 retcode = 1;            // 返回碼    optional string error_messgae = 2;     // 返回失敗時,錯誤資訊};

  account.proto --> 賬戶相關操作

package rpc.account;import "rpc.proto";enum MessageType {    CREATE_ACCOUNT_REQUEST = 1001;    CREATE_ACCOUNT_RESPONSE = 1002;    DELETE_ACCOUNT_REQUEST = 1003;    DELETE_ACCOUNT_RESPONSE = 1004;    // ...};extend rpc.Body {    optional CreateAccountRequest create_account_request = 1001;    optional CreateAccountResponse create_account_response = 1002;    // ...};// account 相關操作介面message CreateAccountRequest {    required string email = 1;    optional string name = 2;    // 不指定則為 email    optional string passwd = 3;  // 初始密碼為 email};message CreateAccountResponse {    required ResponseCode rc = 1;};
// ...

  proto 代碼編譯之後,rpc.account 包被命名為 rpc_account, 以 CreateAccountRequest 為例,可以看到 protoc 編譯後產生了如下 golang 代碼:

// CreateAccountRequest 結構體定義type CreateAccountRequest struct {Email            *string `protobuf:"bytes,1,req,name=email" json:"email,omitempty"`Name             *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`Passwd           *string `protobuf:"bytes,3,opt,name=passwd" json:"passwd,omitempty"`XXX_unrecognized []byte  `json:"-"`}// proto.Message 介面指定的函數func (m *CreateAccountRequest) Reset()                    { *m = CreateAccountRequest{} }func (m *CreateAccountRequest) String() string            { return proto.CompactTextString(m) }func (*CreateAccountRequest) ProtoMessage()               {}// extension type 定義var E_CreateAccountRequest = &proto.ExtensionDesc{ExtendedType:  (*rpc.Body)(nil),ExtensionType: (*CreateAccountRequest)(nil),Field:         1001,Name:          "rpc.account.create_account_request",Tag:           "bytes,1001,opt,name=create_account_request",Filename:      "account.proto",}// 註冊定義結構到 protofunc init() {proto.RegisterType((*CreateAccountRequest)(nil), "rpc.account.CreateAccountRequest")proto.RegisterEnum("rpc.account.MessageType", MessageType_name, MessageType_value)proto.RegisterExtension(E_CreateAccountRequest)}

 其中 init 函數中三個 Register*** 函數將  CreateAccountRequest 相關資訊註冊到 proto 包中:

// github.com/golang/protobuf/proto/properties.go 中func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {if _, ok := enumValueMaps[typeName]; ok {panic("proto: duplicate enum registered: " + typeName)}enumValueMaps[typeName] = valueMap}// EnumValueMap returns the mapping from names to integers of the// enum type enumType, or a nil if not found.func EnumValueMap(enumType string) map[string]int32 {return enumValueMaps[enumType]}func RegisterType(x Message, name string) {if _, ok := protoTypes[name]; ok {// TODO: Some day, make this a panic.log.Printf("proto: duplicate proto type registered: %s", name)return}t := reflect.TypeOf(x)protoTypes[name] = trevProtoTypes[t] = name}// MessageName returns the fully-qualified proto name for the given message type.func MessageName(x Message) string {type xname interface {XXX_MessageName() string}if m, ok := x.(xname); ok {return m.XXX_MessageName()}return revProtoTypes[reflect.TypeOf(x)]}// MessageType returns the message type (pointer to struct) for a named message.func MessageType(name string) reflect.Type { return protoTypes[name] }//  github.com/golang/protobuf/proto/extensions.go 中// RegisterExtension is called from the generated code.func RegisterExtension(desc *ExtensionDesc) {st := reflect.TypeOf(desc.ExtendedType).Elem()m := extensionMaps[st]if m == nil {m = make(map[int32]*ExtensionDesc)extensionMaps[st] = m}if _, ok := m[desc.Field]; ok {panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))}m[desc.Field] = desc}// RegisteredExtensions returns a map of the registered extensions of a// protocol buffer struct, indexed by the extension number.// The argument pb should be a nil pointer to the struct type.func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {return extensionMaps[reflect.TypeOf(pb).Elem()]}

 對照 Register*** 的實現,可以看到通過 E_CreateAccountRequest 類型是註冊到了 extensionMaps 下,這是個兩層的map, map[extendedType]map[messageField]ExtensionType,messageFlied 為 rpc.Body  的欄位標識,因此我們根據 RegisteredExtensions(rpc.Body) 可以擷取到 rpc.Body  下所有的 extension 訊息類型,messageFlied 則和我們之前在 MessageType 中定義的對應各訊息的枚舉類型一致,可以通過 EnumValueMap(rpc.account.MessageType)[rpc.account.create_account_request] 取到,因此可以通過訊息名稱擷取到訊息對應的 ExtensionDesc 類型, 其中 ExtensionType 即為訊息類型。對應的我們用以下代碼通過給定訊息名稱執行個體化一個訊息結構:

// message id -> *proto.ExtensionDesc// 記錄 rpc.Body 的拓展訊息var RPCMessageBodyExtensions map[int32]*proto.ExtensionDescfunc init() {RPCMessageBodyExtensions = proto.RegisteredExtensions((*rpc.Body)(nil))}// some utils for UMessage// msgName: rpc.account.create_account_requestfunc GetRPCMessageObjectByName(msgName string) (msg proto.Message, err error) {msgType := reflect.TypeOf(GetRPCMessageExtension(msgName).ExtensionType)if msgType == nil {err = fmt.Errorf("can't find message type")return}// msgType is pointermsg = reflect.Indirect(reflect.New(msgType.Elem())).Addr().Interface().(proto.Message)return}// msgName: rpc.account.create_account_request// namePrefix: rpc.account// name: create_account_requestfunc GetNamePrefix(msgName string) (prefix string) {items := strings.Split(msgName, ".")prefix = strings.Join(items[0:len(items)-1], ".")return}func GetName(msgName string) (name string) {items := strings.Split(msgName, ".")name = items[len(items)-1]return}func GetRPCMessageId(msgName string) (msgId int32) {msgTypeName := GetNamePrefix(msgName) + ".MessageType"mapMsgNameId := proto.EnumValueMap(msgTypeName)msgId = mapMsgNameId[strings.ToUpper(GetName(msgName))]return}func GetRPCMessageExtension(msgName string) (extension *proto.ExtensionDesc) {msgId := GetRPCMessageId(msgName)extension = RPCMessageBodyExtensions[msgId]return}

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.