Erlang:使用gen_tcp避免竞争条件:controlling_process

时间:2012-07-10 08:45:12

标签: erlang

我正在使用以下顺序实现简单的tcp服务器:

{ok, LS} = gen_tcp:listen(Port,[{active, true}, {reuseaddr, true}, {mode, list}]),
{ok, Socket} =  gen_tcp:accept(LS),
Pid = spawn_link(M, F, [Socket]),           
gen_tcp:controlling_process(Socket, Pid) 

使用选项{active,true}可能会导致竞争条件,即在调用“controlling_process”之前新数据包到达套接字进程,这将导致{tcp,Socket,Data}消息到达父进程而不是孩子。

如何避免这种情况?

3 个答案:

答案 0 :(得分:16)

你是对的。在这种情况下,您肯定需要在侦听套接字选项中传递{active, false}。请考虑以下代码段:

-define(TCP_OPTIONS, [binary, {active, false}, ...]).

...

start(Port) ->
    {ok, Socket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(Socket).

accept(ListenSocket) ->
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            Pid = spawn(fun() ->
                io:format("Connection accepted ~n", []),
                enter_loop(Socket)
            end),
            gen_tcp:controlling_process(Socket, Pid),
            Pid ! ack,
            accept(ListenSocket);
        Error ->
            exit(Error)
    end.

enter_loop(Sock) ->
    %% make sure to acknowledge owner rights transmission finished
    receive ack -> ok end,
    loop(Sock).

loop(Sock) ->
    %% set soscket options to receive messages directly into itself
    inet:setopts(Sock, [{active, once}]),
    receive
        {tcp, Socket, Data} ->
            io:format("Got packet: ~p~n", [Data]),
            ...,
            loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Socket ~p closed~n", [Socket]);
        {tcp_error, Socket, Reason} ->
            io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])
    end.

因此,在controlling_process成功之前,你不会丢失任何东西。众所周知,问题已经在互联网上进行了很多讨论。 如果您希望使用现成的解决方案,您肯定需要查看Ranch项目。

答案 1 :(得分:2)

绝对有比赛条件。今天,我在OTP 21.2中遇到了它,这就是为什么我在这里。数据包可以在accept返回的时间和inet:tcp_controlling_process将套接字设置为被动的时间之间到达。

我只想指出@Keynslug上面回答的微小简化。可以通过非所有者进程将套接字设置为活动状态,因此不需要ack消息传递和enter_loop

-define(TCP_OPTIONS, [binary, {active, false}, ...]).

...

start(Port) ->
    {ok, Socket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(Socket).

accept(ListenSocket) ->
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            Pid = spawn(fun() ->
                io:format("Connection accepted ~n", []),
                loop(Socket)
            end),
            gen_tcp:controlling_process(Socket, Pid),
            inet:setopts(Socket, [{active, once}]),
            accept(ListenSocket);
        Error ->
            exit(Error)
    end.

loop(Sock) ->
    %% set soscket options to receive messages directly into itself
    inet:setopts(Sock, [{active, once}]),
    receive
        {tcp, Socket, Data} ->
            io:format("Got packet: ~p~n", [Data]),
            ...,
            loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Socket ~p closed~n", [Socket]);
        {tcp_error, Socket, Reason} ->
            io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])
    end.

答案 2 :(得分:1)

如果套接字处于活动状态,inet:tcp_controlling_process(由gen_tcp:controlling_process调用)将套接字设置为被动,然后有选择地接收与该套接字相关的所有消息并将它们发送给新的所有者,从而有效地将它们移动到新所有者的消息队列。然后它将套接字恢复为活动状态。

因此没有竞争条件:他们已经考虑过并将其修复到库中。