以前不是很了解Erlang网络编程中流量控制,现在做一下笔记。
Erlang中Socket设置了{active, true}之后,接收到的网络消息会通过{tcp, Socket, Data}的格式主动发送给进程,这样做有个弊处就是没有做流量控制。要是有个客户端疯狂发包过来,服务器不做处理就等着堆溢出。
在《Programming Erlang》的14.2里面,介绍可以使用 {active, once} 来做控制。
{ok, Listen} = gen_tcp:listen(Port, [..,{active, once}...]),
{ok, Socket} = gen_tcp:accept(Listen),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, Data} ->
... do something with the data ...
%% when you're ready enable the next message
inet:setopts(Sock, [{active, once}]),
loop(Socket);
{tcp_closed, Socket} ->
...
end.
不过
锋爷认为这种做法会影响性能,因为每次接收信息都要调用一次 inet:setopts(Sock, [{active, once}]) 。
既然这样,那就先使用{active, true},如果判断到接收到的消息包太多,再改成 {active, once}。我写了个简单的代码:
listen_socket(Socket, Mode) ->
receive
{tcp, Socket, Bin} ->
process_req(Bin),
{message_queue_len, MsgQueueSize} = erlang:process_info(self(), message_queue_len),
if
(MsgQueueSize > 500) and (Mode =:= active_true) ->
io:format("Queue size is ~p~n", [MsgQueueSize]),
inet:setopts(Socket, [{active, once}]),
listen_socket(Socket, active_once);
(MsgQueueSize < 10) and (Mode =:= active_once) ->
io:format("Queue size is ~p~n", [MsgQueueSize]),
inet:setopts(Socket, [{active, true}]),
listen_socket(Socket, active_true);
true ->
io:format("Queue size is ~p~n", [MsgQueueSize]),
listen_socket(Socket, Mode)
end;
判断当前进程消息队列的数量,大于500时被动接收,小于10时再次改为主动接收。
打开服务器后,客户端使用阻塞方式({active, false})连续来发送2000个消息包给服务器,很快服务器端的消息累计达到500以上,此时{active, once}被设置,数据包开始在系统底层堆积,很快就看到客户端的gen_tcp:send调用被阻塞,直到服务器设置{active, true}后,堆积的消息被加载到内存消息队列中,gen_tcp:send恢复正常发送。
可见当{active, once}被设置后,若消息队列中还有数据包,则系统底层的数据包会被堆积,起到了流量控制的作用。