2017-05-15 53 views
4

我在学习Erlang的早期阶段,我需要一些进一步的帮助。不知道这是否会得到任何阳光,但在这里它...我正在寻找一个关于如何工作的流程图。问题了解Erlang Gen_Server架构

示例代码: 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中返回了一个超时值,它将匹配handle_info超时,#状态{lsock = LSock} =状态)。

好的,这是有道理的。

这是我开始变得对二郎

的流动几天我一直在阅读这个在线资源(包括二郎和-OTP在行动)的困惑 - - 这个例子来自 - 也:http://learnyousomeerlang.com/clients-and-servers

我不确定Erlang服务器的流程如何工作。我的理解是,任何发送到服务器的消息都会被gen_server:handle_info/2处理,如果它们超出限制的话 - 意味着它们没有配置或匹配任何其他gen_server:handle_call/3?这意味着,任何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). 

我欣赏任何答案,我知道它可以用尽解释!对不起,有任何语法错误!

回答

8

看起来好像你对tcp端口和服务器通过handle_info回调处理数据的情况有个很好的想法。这是Erlang代码和连接到端口的某个外部客户端之间的一种客户端/服务器交互。但是在Erlang系统中,你也在Erlang进程之间有客户端/服务器关系,双方都在运行Erlang代码。 (即使它只是gen_server进程和Erlang命令shell进程。)

当您使用gen_server:call/cast客户端函数时,它们以您从未见过的方式包装您的消息,但接收的gen_server进程将识别此消息并使用它对消息进行分类,然后传递解包消息到相应的handle_call/handle_cast。除此之外,流量与tcp端口上的传入数据相同:在这两种情况下,它只是向服务器发送异步消息,正在接收和分派到正确的功能。同时在客户端,gen_server:call()函数将等待回复(发件人的Pid包含在包装器中),而gen_server:cast()立即执行。

这些确实只是便利功能。原则上,gen_server可以只处理一个回调来处理所有类型的消息,让您自己决定是调用还是投射以及如何反应。但是通过提供这些库函数并为您分类消息,它可以降低处理类似演员的电话的风险,反之亦然,或者将带外消息与正确的呼叫/转换相混淆。在所有情况下,流程都是相同的:客户端 - >服务器 - >回叫[ - >服务器回复 - >客户端]。因此,您可以使用?SERVER ! {get_count, self()}实现get_count()函数,在handle_info()回调中处理该消息,而不是在handle_call()中处理该消息。 (请不要忘记将回复发送回邮件中包含的Pid,否则客户端将永久卡住。)

或者您可以完全跳过实现get_count()之类的用户API函数并告诉用户只是发送{get_count,self()}到服务器进程并等待回复(其形状也必须记录)。但是之后你无法改变这些消息如何在外壳下看到的细节。 gen_server:call/cast函数可以帮助您隐藏如此混乱的实现细节,并使其不太可能破坏客户端/服务器通信。

希望这会有所帮助。

+0

谢谢!当我在每个函数上进行一些io:format调用来查看流程时,这会更有意义。 – user954753

2

我在学习二郎的早期阶段,我需要一些进一步的帮助

  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. 客户端调用gen_server:start_link()其中至少要指定在其中handle_call/handle_cast函数定义的模块。

  2. 客户端调用gen_server:call(ServerName, Request),它通常包含在一个接口函数中。

  3. gen_server:呼叫(服务器,请求)被定义为send a message to the server,这样的事情:

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

    的modname先前绑定到在gen_server指定的原子:START_LINK():第二个参数是在那里你当服务器接收到该消息指定所包含的功能handle_call()的定义,handle_cast()模块名称等

  4. ,服务器调用ModName:handle_call(),和你的modname:handle_call()的代码做一些事情请求:

    handle_call(Request, ClientPid, ServerLoopValue) -> 
        %%Compute some result using information in Request/ServerLoopValue 
    
  5. 您的modname的最后一行:handle_call()函数告诉服务器如何发送回客户端为a response

    {Response, NewServerLoopValue}. 
    

    然后服务器确实是这样的:

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

    和NewServerLoopValue成为服务器的循环()的新变量global变量。每个服务器有一个循环()函数,它看起来是这样的:

    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就像是一个全球变量,所有的不同的要求可以看出。各种gen_server请求处理程序可以使用存储在ServerLoopValue中的信息来计算响应,也可以将信息添加到其他请求处理程序将来可以使用的ServerLoopValue中。

流量使用TCP套接字一个gen_server的后门与{active, true}, {packet, 4}打算:

  1. 客户端调用gen_tcp:send()

  2. 在服务器端的套接字中,Erlang从套接字中读取数据,构造一个消息元组,并将消息元组放入server's mailbox

  3. 服务器从邮箱中检索{tcp,...}消息,并调用handle_info()

  4. handle_info()调用gen_tcp:send(Socket, Response)将响应发送回客户端。

  5. handle_info()的最后一行告诉调用服务器的循环()函数时使用何种价值的服务器:

    {noreply, SomeValue} => loop(SomeValue) 
    

流量使用的gen_server的后门去与{active, false}, {packet, 0} TCP套接字:

Erlang gen_tcp not receiving anything