3

我正在阅读Programming Erlang 2E。在第 17 章的主动和被动套接字中,它说:

您可能认为对所有服务器使用被动模式是正确的方法。不幸的是,当我们处于被动模式时,我们只能等待来自一个套接字的数据。这对于编写必须等待来自多个套接字的数据的服务器是没有用的。

幸运的是,我们可以采用混合方法,既不是阻塞也不是非阻塞。我们使用选项 {active, once} 打开套接字。在这种模式下,套接字是活动的,但只有一条消息。在向控制进程发送消息后,它必须显式调用 inet:setopts 以重新启用下一条消息的接收。系统将阻塞,直到发生这种情况。这是两全其美的。

相关代码:

% passive mode
loop(Socket) ->     
    ​case​ gen_tcp:recv(Socket, N) ​of​​    
        {ok, B} ->​     
            ... do something with the data ...​     
            loop(Socket);​  
        {error, closed}​    
            ...​    
    ​end​.

% once mode
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​.

我看不出两者之间有什么真正的区别。gen_tcp:recvin mode 本质上与in passivemode 做同样的事情。模式如何解决模式的这个问题:receiveonceoncepassive

不幸的是,当我们处于被动模式时,我们只能等待来自一个套接字的数据。这对于编写必须等待来自多个套接字的数据的服务器是没有用的。

4

2 回答 2

10

主要区别在于当您选择对该套接字上的事件做出反应时。使用主动套接字,您的进程会收到一条消息,而使用被动套接字,您必须自己决定调用gen_tcp:recv. 这对你意味着什么?

编写 Erlang 程序的典型方法是让它们对事件做出反应。遵循这个主题,大多数 Erlang 进程等待代表外部事件的消息,并根据它们的性质对它们做出反应。当您使用活动套接字时,您能够以与其他事件完全相同的方式处理套接字数据的方式进行编程:作为 Erlang 消息。当您使用被动套接字进行编写时,您必须选择何时检查套接字以查看其是否有数据,并对何时检查 Erlang 消息做出不同的选择——换句话说,您最终不得不编写轮询例程,并且这错过了 Erlang 的大部分优势。

active_once所以和active...的区别

使用活动套接字,任何能够建立连接的外部参与者都可以用数据包轰炸进程,无论系统是否能够跟上。如果您想象一个具有一千个并发连接的服务器,其中接收每个数据包需要一些重要的计算或访问其他一些有限的外部资源(不是这样奇怪的场景),您最终不得不就如何处理过载做出选择。

仅使用active套接字,您已经做出了选择:您将让服务降级,直到事情开始失败(超时或其他)。

有了active_once插座,您就有机会做出一些选择。套接字允许您在套接字上active_once接收一条消息并passive再次设置它,直到您将其重置为active_once. 这意味着您可以编写一个阻塞/同步调用来检查整个系统是否可以安全地继续处理消息并将其插入到处理结束和下一个receive侦听套接字的开始之间——甚至选择在系统过载的情况下无需重新激活套接字即可进入receive,但您的进程需要同时处理其他 Erlang 消息。

想象一个名为的进程sysmon存在于该节点上,并检查外部数据库是否过载。您的进程可以接收数据包,对其进行处理,并让系统监视器知道它已准备好进行更多工作,然后才允许套接字向其发送另一条消息。系统监视器还可以向侦听进程发送一条消息,告诉他们在侦听数据包时暂时停止接收数据,这在该方法中是不可能的gen_tcp:recv(因为您要么正在接收套接字数据,要么正在检查 Erlang 消息,但不能同时):

loop(S = {Socket, OtherState}) ->
    sysmon ! {self(), ready},
    receive
        {tcp, Socket, Data} ->
            ok = process_data(Data, OtherState),
            loop(S);
        {tcp_closed, Socket} ->
            retire(OtherState),
            ok;
        {sysmon, activate} ->
            inet:setopts(Socket, [{active, once}]),
            loop(S);
        {sysmon, deactivate} ->
            inet:setopts(Socket, [{active, false}]),
            loop(S);
        {other, message} ->
            system_stuff(OtherState),
            loop(S)
    end.

这是实现系统范围限制的方法的开始,可以轻松处理通常最困难的部分:跨网络、系统外部且完全不受您控制的元素。再加上一些早期的决策(比如“在完全拒绝新连接之前我们需要承担多少负载?”),这种接收套接字数据作为 Erlang 消息的能力,但不会让你自己被它们轰炸(或填满你的邮箱,使查找非套接字消息变得任意昂贵),与我们在石器时代(甚至今天在其他语言中)使用的手动处理套接字相比,感觉非常神奇。

这是 LYSE 的作者 Fred Hebert 的一篇有趣的文章,关于重载:“队列不修复重载”。它不是特定于 Erlang 的,但他所写的想法在 Erlang 中比大多数其他语言更容易实现,这可能与(被误导的)队列作为容量管理技术的普遍使用有关。

于 2015-01-05T22:10:16.187 回答
3

利用这一点的代码如下所示:

loop(Socket1, Socket2) ->     
    ​receive​​  
        {tcp, Socket1, Data} ->​     
            ... do something with the data ...​     
            ​%% when you're ready enable the next message​​     
            inet:setopts(Socket1, [{active, once}]),​  
            loop(Socket1, Socket2);​
        {tcp, Socket2, Data} ->
            ... do something entirely different
            inet:setopts(Socket2, [{active, once}]),​  
            loop(Socket1, Socket2);
        ...
    end.

但是,根据我的经验,您通常不会这样做。更常见的是,每个套接字都有一个进程。主动模式的好处是你可以同时等待来自其他 Erlang 进程的网络数据和消息:

loop(Socket) ->     
    ​receive​​  
        {tcp, Socket, Data} ->​     
            ... do something with the data ...​     
            ​%% when you're ready enable the next message​​     
            inet:setopts(Socket, [{active, once}]),​  
            loop(Socket);​  
        reverse_flux_capacitor ->​    
            reverse_flux_capacitor(),
            %% keep waiting for network data
            loop(Socket)
    ​end​.

此外,在编写“真正的”Erlang/OTP 应用程序时,您通常会编写一个 gen_server 模块而不是循环函数,并且 TCP 消息将在handle_info回调函数中与其他消息一起很好地处理。

于 2015-01-05T17:42:01.093 回答