说到socket就会想到tcp、udp、http协议,先来分析一下erlang项目中如何使用tcp socket。
RabbitMQ项目中使用了prim_inet:async_accept/2、prim_inet:async_recv/3等方法,这些是相对比较底层的api,在erlang官方文档中并没有介绍,不过我们可以通过源码看到,最终会进到erlang:port_control/3函数,与端口进行交互。然后我们进到gen_tcp模块,可以看到无论是调用了inet_tcp还是inet6_tcp,最终还是会回到prim_inet模块。因此gen_tcp是对prim_inet进行包装,是更高层的socket调用模块,用gen_tcp写socket层,代码也会更清理明了。使用gen_tcp模块收到内核协议栈过来完整的封包的时候,可以通过init:setopts/2设置接收消息的方式。官方文档:http://www.erlang.org/doc/man/inet.html
{active, true | false | once | N}:true时,所有收到的信息都会被传递到接收进程;false时,只能接收通过gen_tcp:recv/2,3, gen_udp:recv/2,3 or gen_sctp:recv/1,2等函数接收到信息;once时,每次收到信息后需要手动重置,以待下次接收。这里讨论一下N,N可以理解为once的扩展,once是每收到一条信息都需要重置,而N则是收到N条信息后重置一次。参数N是OTP 17.0以后的版本才能使用。下面有个简单的例子:
-module(sockettcp).
-export([start/0]).
start() ->
{ok, LSock} = gen_tcp:listen(1234, [binary, {packet, 0},{active, false}]),
io:format("listen(~p) on ~p~n",[LSock, 1234]),
accept(LSock).
accept(LSock) ->
{ok, ASock} = gen_tcp:accept(LSock),
Pid = spawn(fun() -> do_loop(ASock) end),
gen_tcp:controlling_process(ASock, Pid),
inet:setopts(ASock, [{active, 3}]),
accept(LSock).
do_loop(ASock) ->
receive
{tcp, Socket, Data} ->
io:format("(~p)recv:~p~n",[Socket, Data]);
{tcp_passive,Socket} ->
inet:setopts(ASock, [{active, 3}]),
io:format("tcp_passive:~p~n",[Socket]);
Err ->
io:format("error:~p~n",[Err])
end,
do_loop(ASock).
再开一个shell作为客户端输入:
{ok,S} = gen_tcp:connect({127,0,0,1},1234,[{packet,0}]).
gen_tcp:send(S, integer_to_binary(1)).
gen_tcp:send(S, integer_to_binary(1)).
gen_tcp:send(S, integer_to_binary(1)).
gen_tcp:send(S, integer_to_binary(1)).
可以看到每间隔3个包,需要重置一次参数。