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}被设置后,若消息队列中还有数据包,则系统底层的数据包会被堆积,起到了流量控制的作用。