Erlang使用ProtoBuffer
最近有工作需要打算为项目服务器做一个机器人,测试测试压力,根据自己的经验,使用Erlang来做是最合适不过的了,但是服务器使用的C++语言,使用了Google的ProtoBuffer作为协议进行数据交换,而Erlang并没有官方的Protobuffer版本支持,官方仅支持C++,JAVA,Python等几种比较主流的语言。在网上搜索了一下,ProtoBuffer的第三方Erlang版本主要有以下几个:
- https://github.com/ngerakines/erlang_protobuffs/tree/master
- https://github.com/basho/erlang_protobuffs
- http://piqi.org/
- https://github.com/tomas-abrahamsson/gpb
我使用了一下其中的一两个版本,但是发现有的对import的支持很有限,甚至不支持,而项目中的PB是有用到,最后选定第四个版本gpb,这个版本可以使用项目中的大部分proto文件生成Erlang源文件,仅有import引用的类型有引用前缀的时候不支持。举个例子:
假如现在有两个proto文件,A.proto和B.proto,A.proto中定义了一个枚举:
package A;
enum Type { T_A = 0; T_B = 1; }
B.proto中引用了这个枚举:
message M_Test
{
required A.T_A eType = 1;
optional int32 other = 2;
}
这个时候编译proto文件,会出现如下错误:
in msg M_Test, field eType: undefined reference A.T_A
但是如果将B.proto中的消息定义改为:
message M_Test
{
required T_A eType = 1;
optional int32 other = 2;
}
则能够成功编译通过。
为了解决这个问题,研究了一下gpb的源码,在gpb_parse.erl中有一个函数:
%% -> {found, {msg,FullName}|{enum,FullName}} | not_found resolve_ref(Defs, Ref, Root, FullName) -> case is_absolute_ref(Ref) of true -> FullRef = ensure_path_prepended(Root, Ref), find_typename(FullRef, Defs); false -> PossibleRoots = compute_roots(FullName), find_ref_rootwards(PossibleRoots, Ref, Defs) end.
这个函数是专门用来解析引用的,其中的变量Ref在第一种写法,其值为:['A','.','T_A'],这个时候解析不了;而第二种方式的写法其值为:['T_A']。
如果匹配一下第一种写法的值,然后将之改为第二种写法的值,即可正常编译。为此我加了一个过滤函数如下:
filterRef([_,'.',Type]) -> [Type]; filterRef(Other) -> Other.
然后把resolve_ref函数改为:
%% -> {found, {msg,FullName}|{enum,FullName}} | not_found resolve_ref(Defs, Ref0, Root, FullName) -> Ref = filterRef(Ref0), case is_absolute_ref(Ref) of true -> FullRef = ensure_path_prepended(Root, Ref), find_typename(FullRef, Defs); false -> PossibleRoots = compute_roots(FullName), find_ref_rootwards(PossibleRoots, Ref, Defs) end.
这样,就可以正常的编译项目中所有的Proto文件了。
btw,按gpb官方的介绍来看,其支持proto2以及proto3的语法,但不知道是否完全支持,有待验证。
E_Mail:witton@163.com