golang中使用消息名称创建protobuf消息

wertyd wertyd     2022-09-25     236

关键词:

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 id -> *proto.ExtensionDesc
// 记录 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
}

 

rust下根据protobuf的消息名创建对象实例(代码片段)

在C++里面,我们可以根据一个消息的名称,动态的创建一个实例123456google::protobuf::Descriptor*desc=    google::protobuf::DescriptorPool::generated_pool()        ->FindMessageTypeByName("mypkg.MyType");googl 查看详情

是否可以使用 protobuf 解析非 protobuf 消息?

】是否可以使用protobuf解析非protobuf消息?【英文标题】:Isitpossibletoparsenon-protobufmessagesusingprotobuf?【发布时间】:2013-04-2320:10:01【问题描述】:我正在做一个项目,我们使用协议缓冲区来创建和解析我们的一些消息(protobuf-net)... 查看详情

protobuf及grpc的client请求

...技术A背景:需要对rpc服务进行压测,需要构造rpc请求。protobuf简介ProtocolBuffer(简称Protobuf)是Google出品的性能优异、跨语言、跨平台的序列化库。文档结构protobuf使用.proto文件来保存文档。定义消息protobuf使用message定义消息,例如... 查看详情

测试 protobuf 消息中是不是存在重复字段

】测试protobuf消息中是不是存在重复字段【英文标题】:Testingofexistenceofarepeatedfieldinaprotobuffmessage测试protobuf消息中是否存在重复字段【发布时间】:2016-10-0313:13:30【问题描述】:我有一条googleprotobuf消息:messageFoorequiredintbar=1;我... 查看详情

如何获取 protobuf 消息中定义的变量类型?

】如何获取protobuf消息中定义的变量类型?【英文标题】:Howtogetthetypeofavariabledefinedinaprotobufmessage?【发布时间】:2015-11-1116:41:28【问题描述】:我正在尝试使用Python将protobuf文件“转换”为Objective-C类。例如,给定protobuf消息:mes... 查看详情

检查 protoBuf 中是不是存在消息类型

】检查protoBuf中是不是存在消息类型【英文标题】:CheckthepresenceofamessagetypeinprotoBuf检查protoBuf中是否存在消息类型【发布时间】:2021-07-0707:13:46【问题描述】:我是gRPC的新手,想检查C#语言中是否存在消息字段。我知道我们可以... 查看详情

Protobuf 消息是不是独立于平台

】Protobuf消息是不是独立于平台【英文标题】:AreProtobufmessagesplatformindependentProtobuf消息是否独立于平台【发布时间】:2013-09-0617:10:01【问题描述】:我正在计划一个应用程序,其中服务器端将用C#编写,客户端将使用phonegap创建... 查看详情

protobuf语法-史上最简教程

Protobuf语法简明教程疯狂创客圈死磕Netty亿级流量架构系列之12【博客园总入口】在protobuf中,协议是由一系列的消息组成的。因此最重要的就是定义通信时使用到的消息格式。一个Protobuf消息(对应JAVA类),由至少一个字段(对... 查看详情

在 Google Cloud Build 中使用 python 插件编译 protobuf 消息

】在GoogleCloudBuild中使用python插件编译protobuf消息【英文标题】:CompilingprotobufmessagesusingpythonpluginwithinGoogleCloudBuild【发布时间】:2021-11-2309:14:15【问题描述】:我已经处理这个问题好几个星期了,急需帮助!因此,提前感谢您对... 查看详情

如何在 protobuf-net.grpc 的消息中使用 IAsyncEnumerable?

】如何在protobuf-net.grpc的消息中使用IAsyncEnumerable?【英文标题】:HowtouseIAsyncEnumerablewithinamessageinprotobuf-net.grpc?【发布时间】:2020-02-0113:58:41【问题描述】:所以,最近我在IAsyncEnumerable<T>的帮助下了解了如何使用protobuf... 查看详情

java示例代码_根据给定的消息类型名称和原始字节获取Java对象的protobuf

java示例代码_根据给定的消息类型名称和原始字节获取Java对象的protobuf 查看详情

Jmeter protobuf 测试。无法读取 Protobuf 消息

】Jmeterprotobuf测试。无法读取Protobuf消息【英文标题】:Jmeterprotobuftesting.CouldnotreadProtobufmessage【发布时间】:2015-11-1709:18:40【问题描述】:我正在通过protobuf协议并使用HTTPRequestSampler测试一个项目。目标应用服务器也是用Java编写... 查看详情

在 Protobuf 消息中引用任意字段的方法

】在Protobuf消息中引用任意字段的方法【英文标题】:WaytoReferenceArbitraryFieldinProtobufMessage【发布时间】:2018-09-2620:14:18【问题描述】:我正在寻找协议缓冲区消息中任意字段的字符串表示形式。有没有实现这个的库?我已经研究... 查看详情

如何使用 java 反射自动将值从 java bean 复制到 protobuf 消息对象?

】如何使用java反射自动将值从javabean复制到protobuf消息对象?【英文标题】:howtoautomaticallycopyvaluesfromjavabeantoprotobufmessageobjectusingjavareflection?【发布时间】:2010-10-2916:05:22【问题描述】:通常我可以使用带有java反射的beanutils在两... 查看详情

如何在 protobuf 消息中建模 Java 原始 int[] 数组

】如何在protobuf消息中建模Java原始int[]数组【英文标题】:HowtomodelJavaprimitiveint[]arrayinprotobufmessage【发布时间】:2018-11-3023:13:35【问题描述】:我是Google协议缓冲区的新手,并试图通过protobuf消息在java中对原始int[]数组进行建模。... 查看详情

unity探索者之socket传输protobuf字节流

...长度+消息id+消息主体内容3publicclassMessage4{5publicIExtensibleprotobuf;6publi 查看详情

如何停止使用 protobuf3 打印错误消息“无法解析类型的消息,因为它缺少必填字段”

】如何停止使用protobuf3打印错误消息“无法解析类型的消息,因为它缺少必填字段”【英文标题】:HowcanIstopprintingtheerrormessage"Can\'tparsemessageoftypebecauseitismissingrequiredfields"withprotobuf3【发布时间】:2018-08-1602:28:44【问题描... 查看详情

流式传输 protoBuf 消息的设计模式

】流式传输protoBuf消息的设计模式【英文标题】:designpatternforstreamingprotoBufmessages【发布时间】:2013-11-1908:47:29【问题描述】:我想将protobuf消息流式传输到文件中。我有一个protobuf消息messagecar...//somefields我的java代码会创建这个... 查看详情