Erlang:使用gen_tcp避免竞争条件:控制_进程
我正在按照以下顺序实现简单tcp服务器:Erlang:使用gen_tcp避免竞争条件:控制_进程,erlang,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}可能会导致
{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}消息到达父进程而不是子进程
如何避免这种情况?你是对的。在这种情况下,您肯定需要在侦听套接字选项之间传递
{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.
因此,在控制过程
成功之前,您不会丢失任何东西。众所周知,这个问题在互联网上讨论了很多。
如果您希望使用现成的解决方案,您肯定需要查看project。如果套接字处于活动状态,
inet:tcp\u controlling\u process
(由gen\u tcp:controlling\u process调用)将套接字设置为被动,然后有选择地接收与该套接字相关的所有消息并将其发送给新所有者,有效地将它们移动到新所有者的消息队列。然后,它将套接字恢复为活动状态
因此没有竞争条件:他们已经想到了这一点,并在库中修复了它。绝对存在竞争条件。我今天在OTP21.2中遇到了它,这就是我来这里的原因。数据包可以在accept
返回的时间和inet:tcp\u controlling\u process
将套接字设置为被动的时间之间到达
我只是想指出上面@Keynslug的答案的微小简化。套接字可以从非拥有进程设置为活动,因此不需要ack
消息传递和enter\u循环
-定义(TCP_选项,[binary,{active,false},…])。
...
开始(端口)->
{ok,Socket}=gen_tcp:listen(端口,tcp_选项),
接受(套接字)。
接受(ListenSocket)->
案例gen_tcp:accept(ListenSocket)的
{好的,套接字}->
Pid=繁殖(乐趣()->
io:格式(“已接受连接~n”,[]),
回路(插座)
(完),,
gen_tcp:控制进程(套接字、Pid),
inet:setopts(套接字,[{active,once}]),
接受(ListenSocket);
错误->
退出(错误)
结束。
循环(袜子)->
%%设置soscket选项以直接将消息接收到其自身
inet:setopts(Sock,[{active,once}]),
接收
{tcp,套接字,数据}->
io:format(“获取数据包:~p~n,[Data]),
...,
回路(插座);
{tcp_已关闭,套接字}->
io:格式(“套接字~p闭合~n”,[Socket]);
{tcp_错误,套接字,原因}->
io:format(“套接字上的错误~p原因:~p~n”,[socket,原因])
结束。
看起来不可能存在争用条件,因为在调用控制进程之前,没有任何内容从套接字读取任何内容。所以,你问题中的“可能”这个词让我很困扰。你真的看到过比赛情况吗?如果是这样,你能提供更多的细节吗?{active,true}标志意味着(如果我理解正确的话)系统从套接字主动读取数据包,并将它们作为{tcp,socket,Data}发送到控制进程消息收件箱。我明白你的意思。我建议将调用从您生成的进程移动到gen\u tcp:accept
(在您的示例中为M:F
),以避免出现这种情况。这样,您就可以避免调用gen\u tcp:controlling\u process
。@Keynslug在我还在键入我的时在下面给出了一个很好的答案。刚刚发现任何进入循环的尾部递归将在接收确认->确定结束时冻结。我将发布编辑来解决这个问题。顺便说一句,用{active,once}代替{active,true}的原因是什么。有一个重要的概念-确保您没有使用{active,true}
。很多人都提到这一点。如果您使用类似HTTP的请求-响应模型,那么{active,once}
就是您所需要的。因此,我们提供了一个保证——如果机器处于高负载状态,VM将不会超出可预测的内存占用范围。如果您处理大量数据流,您肯定需要继续使用{active,false}
并手动执行recv
s。在这种情况下,这种方式将大大提高性能和系统带宽。感谢您指出这一点。我想知道这是否是一个(相对而言)最近的功能(考虑到这个问题的旧答案和互联网上的一些类似讨论)?