erlang中使用google protobuf进行通信

 

erlang中使用google protobuf进行通信

http://www.codedump.info/?p=231

初学erlang,花了不少的功夫,想要在erlang中集成google的protobuf用于消息通信.个人觉得,使用类似protobuf这样通用的编解码模块,有一个好处就是这部分完全交给别人,再不用自己关心什么很操蛋的大小端,数据长度等琐碎的问题,另外,protobuf使用.proto文件自描述协议,C/S端人员可以通过这个来讨论问题,一目了然.

然而,要把它集成到erlang中还是一件比较麻烦的事情,一来google官方没有对erlang进行支持,这也许是因为google官方认定的编程语言只有C++,java,Python三种的缘故吧,而它的竞争对手,如thrift等都提供了erlang的支持.虽然官网上给出了第三方做的其他语言的实现,但是毕竟不是官方的实现,可能会有些未知的问题.比如我很担心我使用erlang非官方的protobuf实现写了一个服务器,但是再用比如python写了一个客户端,C/S两端都使用protobuf进行通信,但是由于编解码实现的差异,导致了协议数据有出入.

不过,鉴于我在搜索资料的时候未发现比较好的介绍如何在erlang中使用protobuf的方式,还是记录一下吧.

1) 使用哪个protobuf的eralng实现
google官网上提供了两个protobuf的erlang实现,一个erlang-protobuf,一个是piqi,后者没有研究过,我使用的是前者.
不过很遗憾,貌似前者的官网实现还是有些问题的,但是所幸的是,basho的riak项目也使用erlang编写的,里面就使用到了protobuf,而且好像对原版的erlang-protobuff进行了一些修正,比如会有一个proto-erlang的二进制文件专门用于编译proto文件产生对应的.hrl和.erl文件,所以这里推荐使用riak版本的erlang-protobuff实现.而我这里给出的实现,也是参考riak项目的,从里面将这部分提取出来形成了一个demo版本.

2) 协议的编解码
在demo例子中,我定义了一个名为echo.proto的协议文件,里面的定义如下:

message Echo {
  required string content = 1;
  required int32 value = 2;
}

这里注意最后的”}”之后不要加”;’号,否则erlang版本的protobuf编译器会报错.会编译生成对应的.hrl文件,里面是一个对应的record定义,因此就可以这样定义一个符合这个record定义的变量了:

Msg = #echo{content="hello world", value=1}

但是注意,在erlang里面会产生是这样的数据:{echo, {“hello world”, 1}}也就是说,它会自动加上消息类型的原子数据.这样的话,如果要匹配起来则是一个字符串比较的过程,会比较影响效率,因此可以在编码的时候加上一个映射关系,说白了就是指定一个opcode:

-module(echo_pb_util).
-compile(export_all).

-include_lib("echo_pb.hrl").

%% Create an iolist of msg code and protocol buffer message
encode(Msg) when is_atom(Msg) ->
  [msg_code(Msg)];
encode(Msg) when is_tuple(Msg) ->
  MsgType = element(1, Msg),
  [msg_code(MsgType) | echo_pb:iolist(MsgType, Msg)].

%% Decode a protocol buffer message given its type - if no bytes
%% return the atom for the message code
decode(MsgCode, <<>>) ->
  msg_type(MsgCode);
decode(MsgCode, MsgData) ->
  echo_pb:decode(msg_type(MsgCode), MsgData).

msg_type(0) -> echo;
msg_type(_) -> undefined.

msg_code(echo) -> 0.

这个文件不是编译器生成的,而是自己阅读riak的代码抽离出来的,感觉这是一个不错的思路,就是在编码的时候将原子编码为一个opcode,解码的时候将opcode解码还原为一个原子.

最后给出对应的客户端,服务器的核心代码:
客户端发送部分的代码:

    Msg = #echo{content="hello world", value=1},
    Pkt = echo_pb_util:encode(Msg),
    ok = gen_tcp:send(Socket, Pkt),

服务器端发送部分的代码:

	    [MsgCode|MsgData] = binary_to_list(Bin),
	    io:format("code  ~p, data: ~p~n",[MsgCode, MsgData]),
	    Msg = echo_pb_util:decode(MsgCode, list_to_binary(MsgData)),
	    #echo{content=Content, value=Value} = Msg,
	    io:format("content:~p, value:~p~n",[Content, Value]),

完整的代码在这里,其中echo_pb.hrl/erl是由protobuf的erlang编译器生成的代码.

服务器/客户端的代码比较粗糙,使用erlang程序设计一书的示例代码中抽出来的,凑合着看看吧:)

原文地址:https://www.cnblogs.com/xiayong123/p/3717109.html