设为首页 收藏本站
查看: 1020|回复: 0

[经验分享] golang中使用消息名称创建protobuf消息

[复制链接]

尚未签到

发表于 2018-9-20 10:14:06 | 显示全部楼层 |阅读模式
  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",
  
}
  

  
// 注册定义结构到 proto
  
func 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] = t
  revProtoTypes[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>
// 记录 rpc.Body 的拓展消息  
var RPCMessageBodyExtensions map[int32]*proto.ExtensionDesc
  

  
func init() {
  RPCMessageBodyExtensions = proto.RegisteredExtensions((*rpc.Body)(nil))
  
}
  

  
// some utils for UMessage
  

  
// msgName: rpc.account.create_account_request
  
func 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 pointer
  msg = reflect.Indirect(reflect.New(msgType.Elem())).Addr().Interface().(proto.Message)
  return
  
}
  

  
// msgName: rpc.account.create_account_request
  
// namePrefix: rpc.account
  
// name: create_account_request
  
func 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
  
}
  

  




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598716-1-1.html 上篇帖子: golang与java间的json-rpc跨语言调用 下篇帖子: 创建超小的Golang docker 镜像
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表