最近有工作需要打算為項目服務器做一個機器人,測試測試壓力,根據自己的經驗,使用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的語法,但不知道是否完全支持,有待驗證。