困惑Erlang Gen_Server架构

7
我正在学习Erlang的初级阶段,需要进一步的帮助。不确定这是否会得到任何关注,但是我想要一个关于示例功能的流程图。
示例代码: https://github.com/erlware/Erlang-and-OTP-in-Action-Source/blob/master/chapter_03/tr_server.erl 让我解释一下我的问题...
1> tr_server:start_link().

我理解这个,它调用start_link(?DEFAULT_PORT),这会调用gen_server:start_link -- 然后实际上会回调到tr_server(?MODULE)的init([Port])。

init([Port]) ->
    {ok, LSock} = gen_tcp:listen(Port, [{active, true}]),
    {ok, #state{port = Port, lsock = LSock}, 0}.

这也是可以理解的。您将数据发送到服务器,gen_server:handle_info/2得到处理,因此调用?MODULE:handle_info/2 -- 这是一个情况,并且由于我们在?MODULE:init中返回了超时,它将匹配句柄信息(超时,#state{lsock = LSock} = State)。

好的,这很有道理。

这就是我开始对Erlang的流程感到困惑的地方

几天来,我一直在阅读在线资源(包括Erlang-and-OTP-in-action)--这个例子来自此处--还有:http://learnyousomeerlang.com/clients-and-servers

我不确定Erlang服务器的流程如何工作。据我所知,如果任何消息被发送到服务器并且它们超出范围——意味着如果它们没有被配置或与任何其他gen_server:handle_call/3相匹配则会由gen_server:handle_info/2处理?这意味着,任何TCP数据都会自动由gen_server:handle_info/2处理--并且回调到?MODULE:handle_info?

我不明白的是,handle_call、handle_cast在服务器架构中的作用和位置--也不明白从客户端->服务器架构的服务器流程(直到我感到困惑的地方)是如何的。我认为这很重要,就像电路图一样,来阐明流程。

以下是主要问题:当客户端发送以下内容时,服务器的流程是什么:

lists:reverse([1,2,3]).

用通俗易懂的语言来说,获取一个流程图可以更好地理解它的工作原理。从文本和示例中,我们很难清楚地了解它的工作方式。不太清楚为什么我们需要:

get_count() ->
    gen_server:call(?SERVER, get_count).

stop() ->
    gen_server:cast(?SERVER, stop).

非常感谢您的回答,我知道解释可能很累人!对于任何语法错误我表示抱歉!

2个回答

9
看起来你已经了解了数据从tcp端口传输并通过handle_info回调函数处理的流程,这是一种Erlang代码与连接到端口的外部客户端之间的客户端/服务器交互方式。但在Erlang系统中,您还可以在Erlang进程之间建立客户端/服务器关系,双方都运行Erlang代码(即使只是gen_server进程和Erlang命令行进程)。
当您使用gen_server:call/cast客户端函数时,它们会以一种您看不到的方式包装您的消息,但接收方gen_server进程将识别此内容并使用其对消息进行分类,然后将未包装的消息传递给相应的handle_call/handle_cast。除此之外,在tcp端口上接收到数据和在Erlang进程之间交互时,流程都是相同的:在两种情况下,它只是发送到服务器的异步消息,并被接收并分派到正确的函数。同时,在客户端方面,gen_server: call()函数将等待回复(发件人的Pid包含在封装中),而gen_server: cast()则立即继续执行。
这些实际上只是便利函数。原则上,gen_server可以只有一个回调来处理所有类型的消息,让您自己编码是调用还是广播以及如何反应。但是,通过为您提供这些库函数并为您分类消息,它减少了将呼叫误认为广播或反之,或将带外消息与适当的呼叫/广播混淆的风险。在所有情况下流程都是相同的:客户端 -> 服务器 -> 回调 [-> 服务器回复 -> 客户端]。
因此,您可以使用?SERVER ! {get_count, self()}来实现get_count()函数,并在handle_info()回调中处理该消息,而不是在handle_call()中处理。 (只要不要忘记将回复发送回消息中包含的Pid,否则客户端将永远被卡住。)
或者您可以完全跳过实现用户API函数(例如get_count())并告诉您的用户只需将{get_count,self()}发送到服务器进程并等待回复(其形状也必须记录下来)。但是,然后您无法稍后更改这些消息在底层的实现细节。gen_server: call/cast函数可帮助您隐藏这些混乱的实现细节,并减少您错误地处理客户端/服务器通信的可能性。
希望这能帮到您。

谢谢!当我在每个函数上进行了一些io:format调用以查看流程时,它变得更有意义了。 - user954753

5

我正在学习Erlang的早期阶段,需要进一步的帮助。

  1. 查看一些简单的非gen_server客户端-服务器示例。尝试想出自己的客户端-服务器的简单想法并编写代码。
  2. 了解使用模块名称对简单服务器进行参数化。
  3. 学习gen_server和行为。
  4. 练习将简单服务器转换为gen_server。使用具有拆分窗口的文本编辑器非常方便。
  5. 了解gen_tcp和套接字。
  6. 查看将gen_tcp和套接字与gen_server相结合的示例。

请参见此处:

http://erlang.org/doc/design_principles/des_princ.html

我不会从第6步开始,这似乎是你正在做的事情。
这意味着任何TCP数据都会自动由gen_server:handle_info/2处理--它会回调到?MODULE:handle_info吗?
没有回调。TCP数据绕过整个gen_server体系结构。TCP数据通过“后门”进入,可以说是与其他入侵者一起进入。因此,gen_server:handle_info()用于处理它们。handle_info()检查服务器邮箱以查找与handle_info()参数指定的模式匹配的任何消息。
任何需要对TCP数据执行的操作都在handle_info()中完成。当然,如果您需要在handle_info()中进行一些复杂的数据处理,您可以随时调用辅助函数来计算某些结果:
handle_info({tcp, Socket, RawData}, State) ->
    Result1 = computerInGermanyProcess(RawData),
    Result2 = computerInFranceProcess(RawData),
    Result3 = computerInRussiaProcess(RawData),    
    Result4 = State#state.stored_val,

    gen_tcp:send(Socket, [Result1, Result2, Result3, Result4]),
    {noreply, State};  %%Because a TCP message landed in the mailbox with no From value, 
                       %%do not reply to From, and do not mess with the value in State.

 computerInGermanyProcess(RawData) ->
          %% Possibly use gen_tcp and sockets again to send a message
          %% to another computer to get some data in order to
          %% calculate Result1:
          Result1.
 computerInFranceProcess(RawData) ->
          ...
          Result2.
 computerInRussiaProcess(RawData) ->
          ...
          Result3.         

我不明白handle_call和handle_cast在服务器架构中的作用和位置,也不理解从客户端到服务器架构的流程(直到我感到困惑的地方)。我认为这很重要,需要像电路图一样绘制流程图来说明。
Client:                                                                    
+------------------------------------------------------+------------------------------------------------------+
| my_request() ->                                      |   handle_call({dostuff, Val}, ClientPid, State) ->   |
|     Request = {dostuff, 10},                         |       %%Do something with Val, State                 |
|     Server = ?MODULE,                                |       Response = {result, 45},                       |
|     Response = gen_server:call(Server, Request).     |       NewState = ....,                               |
|                            |                         |       {Response, NewState}.                          |
|                            |       from gen_server:  |                                                      |
|                            |            start_link() |                                     ^                |
|                            |                 |       |                                     |                |
+----------------------------+-----------------+-------+-------------------------------------+----------------+
                             |                 |                                             |
                             |                 |                                             |
+----------------------------+-----------------+-------+                                     |
|-module(gen_server).        |                 |       |                                     |
|-export([call/2,....]).     V                 |       |                                     |
|                                              |       |                                     |
|call(Server, Request) ->                      V       |                                     |
|  Server ! {request, Request, call, self(), Module} --+-->+                                 |                     
|  receive                                             |   |                                 ^                
|      {reply, Response, Server} ->                    |   |                                 |
|          Response      ^                             |   V                                 |
|  end.                  |                             |   |                                 |
+------------------------+-----------------------------+   |                                 |
|   Mailbox              |                             |   |                                 |
|                        |                             |   |                                 |
|       {reply, Response, Server}  <----------<--------+---+--------------<--------------+   |
|                                                      |   V                             ^   ^      
+------------------------------------------------------+   |                             |   |
                                                           |                             |   |
                                                           |                             |   |
Server:                                                    |                             |   |
+------------------------------------------------------+   |                             |   |
|    Mailbox                                           |   |                             |   |
|                                                      |   V                             ^   ^
|        {request, Request, call, ClientPid, Module} <-+---+                             |   |
|                            |                         |                                 |   |
+----------------------------+-------------------------+-----------------------------+   |   |                 
|                            |                                                       |   |   |
|loop(State) ->              |                                                       |   |   |
|    receive                 V                                                       |   ^   ^
|        {request, Request, call, ClientPid, Module}  ->                             |   |   |           ^
|            {Response, NewState} = Module:handle_call(Request, ClientPid, State} ---+---|-->+           |
|            ClientPid ! {reply, Response, self()}, ----------->---------------------+-->+            To Client
|            loop(NewState);                                                         |                   ^
|        {request, Request, cast, ClientPid, Module} ->                              |                   |
|            NewState = Module:handle_cast(Request, State), ------->---------->------|----->------------>+
|            loop(NewState);                                                         |
|        ...                                                                         |
|        ...                                                                         |                                      
|    end.                                                                            |
+------------------------------------------------------------------------------------+

当客户端调用gen_server:call()时的流程:

  1. Client calls gen_server:start_link() which at a minimum specifies the module in which the handle_call/handle_cast functions are defined.

  2. Client calls gen_server:call(ServerName, Request), which is usually wrapped in an interface function.

  3. gen_server:call(ServerName, Request) is defined to send a message to the server, something like this:

     ServerName ! {request, Request, call, self(), ModName}.
    

    ModName was previously bound to the atom that was specified in gen_server:start_link(): the second argument is where you specify the module name that contains the definitions of the functions handle_call(), handle_cast(), etc.

  4. When the server receives that message, the server calls ModName:handle_call(), and your ModName:handle_call() code does something with the Request:

    handle_call(Request, ClientPid, ServerLoopValue) ->
        %%Compute some result using information in Request/ServerLoopValue
    
  5. The last line of your ModName:handle_call() function tells the server what to send back to the client as a response:

      {Response, NewServerLoopValue}.
    

    Then the server does something like this:

      From ! {reply, Response, ServerPid}.
      loop(NewServerLoopValue).
    

    and NewServerLoopValue becomes the new global variable for the server's loop(). Every server has a loop() function that looks something like this:

    loop(ServerLoopValue) ->
        receive
            {request, dothis, From} ->
                Result1 = ...SomeValue + 5....,
                From ! {Result1, self()},
                loop(NewServerLoopValue);
            {request, {dothat, 10}, From} ->
                Result2 = ... SomeValue - 10...,
                From ! {Result2, self()},
                loop(NewServerLoopValue);
            {request, stop, From}
                %%do not call loop()
        end.
    

    ServerLoopValue is like a global variable that all the different requests can see. The various gen_server request handlers can use information stored in ServerLoopValue to calculate a response, or they can add information to ServerLoopValue that other request handlers can use in the future.

使用带有{active, true}, {packet, 4}的TCP套接字进入gen_server后门的流程:

  1. Client calls gen_tcp:send().

  2. At the server's end of the socket, Erlang reads the data from the socket, constructs a message tuple, and puts the message tuple in the server's mailbox.

  3. The server retrieves the {tcp, ...} message from the mailbox and calls handle_info().

  4. handle_info() calls gen_tcp:send(Socket, Response) to send a response back to the client.

  5. The last line of handle_info() tells the server what value to use when calling the server's loop() function:

    {noreply, SomeValue}   =>  loop(SomeValue)
    
使用TCP套接字并设置{active, false}, {packet, 0}时,进入gen_server后门的流程: Erlang gen_tcp not receiving anything的解释。

哇,这个图你自己画的吗? - Ilya Vassilevsky
@IlyaVassilevsky,是的!这有任何意义吗? - 7stud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接