erlang R17帶來了新的socket選項{active,N} ,與{active,once}一起為應用層提供流量控制。為什麼要多了這個選項,{active,once}不是可以有效抑制大量socket消息嗎?
我們知道,{active,once}在每次接收到包都要重新設置active選項,才能繼續接收erlang的消息通知。實際上,每次設定{active,once}都意味著調用一次epoll_ctl, 如果請求過於頻繁,就會有大量的epoll_ctl調用。erlang目前只有一個線程會收割epoll_wait事件,epoll_wait要輪詢已經就緒的ctl隊列,如果大量的ctl事件將會阻塞了epoll_wait的操作,造成網絡處理能力的下降。
那麼,我們能不能設定接收N個的socket消息後再執行一次epoll_ctl,這樣可以有效減少epoll_ctl的調用,{active,N}就是這樣出現的。
下面來看一下{active,N}的說明
Add the {active,N} socket option for TCP, UDP, and SCTP,where N is an integer in the range -32768..32767, to allow a caller to specify the number of data messages to be delivered to the controlling process. Once the
socket's delivered message count either reaches 0 or is explicitly set to 0 with inet:setopts/2 or by including {active,0} as an option when the socket is created, the socket transitions to passive({active, false}) mode and the socket's controlling process receives
a message to inform it of the transition. TCP sockets receive {tcp_passive,Socket}, UDP sockets receive {udp_passive,Socket} and SCTP sockets receive {sctp_passive,Socket}.
The socket's delivered message counter defaults to 0, but it can be set using {active,N} via any gen_tcp, gen_udp, or gen_sctp function that takes socket options as arguments, or via inet:setopts/2. New N values are added to the socket's current counter value,
and negative numbers can be used to reduce the counter value. Specifying a number that would cause the socket's counter value to go above 32767 causes an einval error. If a negative number is specified such that the counter value would become negative, the
socket's counter value is set to 0 and the socket transitions to passive mode. If the counter value is already 0 and inet:setopts(Socket, [{active,0}]) is specified, the counter value remains at 0 but the appropriate passive mode transition message is generated
for the socket.
設定了{active,N}選項後,進程在接收了N個包後,會收到{tcp_passive, Socket}消息,意味著這個Socket進入被動模式,需要重新設置active選項。
接下來,在實際的例子中測試這個參數:
-module(server).
-export([start/0]).
-export([continue/1]).
-define( PORT, 8888).
start() ->
{ok, LSock} = gen_tcp:listen(?PORT, [binary, {packet, 0},{active, false}]),
io:format("socket listen: ~p on ~p ~n",[LSock, ?PORT]),
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("socket ~p recv: ~p ~n",[Socket, Data]);
{tcp_closed, Socket} ->
io:format("socket ~p close ~n",[Socket]);
{tcp_passive,Socket} ->
io:format("socket ~p is passive, please call continue/1 ~p ~n",[Socket, self()]);
release_passive ->
inet:setopts(ASock, [{active, 3}]);
Err ->
io:format("socket may error: ~p ~n",[Err])
end,
do_loop(ASock).
continue(Pid) ->
Pid ! release_passive,
ok.編譯啟動這個模塊後,我們創建一個客戶端來請求這個服務端:
1> f(S), {ok,S} = gen_tcp:connect({127,0,0,1},8888,[{packet,0}]).
{ok,#Port<0.526>}
2> gen_tcp:send(S,<<"hello">>).
ok
3> gen_tcp:send(S,<<"hello">>).
ok
4> gen_tcp:send(S,<<"hello">>).
ok
5> gen_tcp:send(S,<<"hello">>).
ok服務端控制台打印了這樣的信息:
D:\tmp>erl -s server socket listen: #Port<0.422> on 8888 Eshell V6.0 (abort with ^G) 1> socket #Port<0.479> recv: <<"hello">> 1> socket #Port<0.479> recv: <<"hello">> 1> socket #Port<0.479> recv: <<"hello">> 1> socket #Port<0.479> is passive, please call continue/1 <0.33.0> 1> server:continue(pid(0,33,0)). socket #Port<0.479> recv: <<"hello">> ok 2>在上面的例子中,我們設定了{active,3}的選項,在接收到客戶端3次數據後,socket進入了passive狀態,在重新設置{active,N}後繼續接收tcp消息。
那麼,如何在實際項目中運用{active,N}選項?
inet:setopts(Socket, [{active, 300}]),
erlang:send_after(30 * 1000, self(), release_passive);大概思路是,在30秒內最多接收300個包,超過就不接收,等待這30秒完成後繼續接收,如此反復。
利用這點還可以加多一個計數器,如果超過10次進入passive狀態,說明這個Socket存在問題,有攻擊的行為。
參考:
http://blog.csdn.net/mycwq/article/details/24814843
http://www.erlang.org/download/otp_src_17.0.readme
http://blog.yufeng.info/archives/2970