难以理解 Erlang Gen_Server 架构
Trouble Understanding Erlang Gen_Server Architecture
我正处于学习 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(?模块)初始化([端口])。
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(timeout, #state{lsock = LSock} = State).
好的,这是有道理的。
这是我开始对 Erlang 的流程感到困惑的地方
几天来,我一直在阅读关于此的在线资源(包括 Erlang-and-OTP-in-action)——这个例子的来源——还有: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).
感谢任何答案,我知道解释起来可能会让人筋疲力尽!抱歉有任何语法错误!
看起来您对来自 tcp 端口的数据流和服务器通过 handle_info 回调处理它的情况有很好的了解。这是一种 client/server 交互,在 Erlang 代码和连接到端口的一些外部客户端之间。但是在 Erlang 系统中,Erlang 进程之间也有 client/server 关系,其中双方都是 运行 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 可能只有一个回调来处理所有类型的消息,让您自行编码是调用还是强制转换以及如何做出反应。但是通过提供这些库函数并为您对消息进行分类,它可以降低将调用视为强制转换或相反的风险,或者将带外消息与正确的 call/cast 混淆的风险。所有情况下的流程都是相同的:Client -> Server -> Callback [ -> Server Reply -> Client ].
因此,您可以使用 ?SERVER !
{get_count, self()}
实现 get_count() 函数,在 handle_info() 回调而不是 handle_call() 中处理该消息。 (只是不要忘记将回复发送回消息中包含的 Pid,否则客户端将永远卡住。)
或者您可以完全跳过实现用户 API 函数,例如 get_count() 并告诉您的用户只需将 {get_count, self()} 发送到服务器进程并等待回复(其形状也必须记录在案)。但是您以后无法更改这些消息在幕后的外观细节。 gen_server:call/cast 函数可以帮助您隐藏这些混乱的实现细节,并降低您搞砸 client/server 通信的可能性。
希望对您有所帮助。
I am in early stages of learning Erlang and I need some further assistance
- 查看一些简单的非 gen_server 客户端-服务器示例。尝试为您自己的客户端-服务器提出一个简单的想法并编写代码。
- 了解如何使用模块名称参数化简单服务器。
- 了解 gen_server 和行为。
- 练习将简单服务器转换为 gen_server。使用带有拆分的文本编辑器 window 真的很方便。
- 了解 gen_tcp 和套接字。
- 查看将 gen_tcp 和套接字与 gen_server 结合使用的示例。
看这里:
http://erlang.org/doc/design_principles/des_princ.html
我不会从第 6 步开始,而您似乎正在这样做。
This means, any TCP data is automatically handled by the
gen_server:handle_info/2 -- which gets a call back to the
?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.
What I dont understand is how and where handle_call, handle_cast play
into the server architecture -- NOR do I understand the flow of the
server from client->server architecture (up until where I get
confused). I think this is very important to illustrate diagrams of
flow much like circuitry diagrams.
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()
:
客户端调用 gen_server:start_link()
至少指定定义了 handle_call/handle_cast 函数的模块。
客户端调用gen_server:call(ServerName, Request)
,一般封装在接口函数中
gen_server:call(ServerName, Request) 被定义为 send a message to the server
,像这样:
ServerName ! {request, Request, call, self(), ModName}.
ModName 之前绑定到在 gen_server:start_link() 中指定的原子:第二个参数是您指定包含函数定义的模块名称的位置 handle_call(),handle_cast()等
当服务器收到该消息时,服务器调用 ModName:handle_call()
,并且您的 ModName:handle_call() 代码对请求执行某些操作:
handle_call(Request, ClientPid, ServerLoopValue) ->
%%Compute some result using information in Request/ServerLoopValue
您的 ModName:handle_call() 函数的最后一行告诉服务器将什么发送回客户端 a response
:
{Response, NewServerLoopValue}.
然后服务器会做这样的事情:
From ! {reply, Response, ServerPid}.
loop(NewServerLoopValue).
并且 NewServerLoopValue 成为服务器循环 () 的新 全局 变量。每个服务器都有一个 loop() 函数,看起来像这样:
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 以供其他请求处理程序将来使用。
进入 gen_server 后门的流量使用 {active, true}, {packet, 4}
:
的 TCP 套接字
客户调用gen_tcp:send()
.
在套接字的服务器端,Erlang从套接字中读取数据,构造消息元组,并将消息元组放入server's mailbox
。
服务器从邮箱中获取{tcp, ...}消息并调用handle_info()
.
handle_info() 调用 gen_tcp:send(Socket, Response)
将响应发送回客户端。
最后一行handle_info()告诉服务器在调用服务器的loop()函数时使用什么值:
{noreply, SomeValue} => loop(SomeValue)
进入 gen_server 后门的流量使用 {active, false}, {packet, 0}
的 TCP 套接字:
我正处于学习 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(?模块)初始化([端口])。
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(timeout, #state{lsock = LSock} = State).
好的,这是有道理的。
这是我开始对 Erlang 的流程感到困惑的地方
几天来,我一直在阅读关于此的在线资源(包括 Erlang-and-OTP-in-action)——这个例子的来源——还有: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).
感谢任何答案,我知道解释起来可能会让人筋疲力尽!抱歉有任何语法错误!
看起来您对来自 tcp 端口的数据流和服务器通过 handle_info 回调处理它的情况有很好的了解。这是一种 client/server 交互,在 Erlang 代码和连接到端口的一些外部客户端之间。但是在 Erlang 系统中,Erlang 进程之间也有 client/server 关系,其中双方都是 运行 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 可能只有一个回调来处理所有类型的消息,让您自行编码是调用还是强制转换以及如何做出反应。但是通过提供这些库函数并为您对消息进行分类,它可以降低将调用视为强制转换或相反的风险,或者将带外消息与正确的 call/cast 混淆的风险。所有情况下的流程都是相同的:Client -> Server -> Callback [ -> Server Reply -> Client ].
因此,您可以使用 ?SERVER !
{get_count, self()}
实现 get_count() 函数,在 handle_info() 回调而不是 handle_call() 中处理该消息。 (只是不要忘记将回复发送回消息中包含的 Pid,否则客户端将永远卡住。)
或者您可以完全跳过实现用户 API 函数,例如 get_count() 并告诉您的用户只需将 {get_count, self()} 发送到服务器进程并等待回复(其形状也必须记录在案)。但是您以后无法更改这些消息在幕后的外观细节。 gen_server:call/cast 函数可以帮助您隐藏这些混乱的实现细节,并降低您搞砸 client/server 通信的可能性。
希望对您有所帮助。
I am in early stages of learning Erlang and I need some further assistance
- 查看一些简单的非 gen_server 客户端-服务器示例。尝试为您自己的客户端-服务器提出一个简单的想法并编写代码。
- 了解如何使用模块名称参数化简单服务器。
- 了解 gen_server 和行为。
- 练习将简单服务器转换为 gen_server。使用带有拆分的文本编辑器 window 真的很方便。
- 了解 gen_tcp 和套接字。
- 查看将 gen_tcp 和套接字与 gen_server 结合使用的示例。
看这里:
http://erlang.org/doc/design_principles/des_princ.html
我不会从第 6 步开始,而您似乎正在这样做。
This means, any TCP data is automatically handled by the gen_server:handle_info/2 -- which gets a call back to the ?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.
What I dont understand is how and where handle_call, handle_cast play into the server architecture -- NOR do I understand the flow of the server from client->server architecture (up until where I get confused). I think this is very important to illustrate diagrams of flow much like circuitry diagrams.
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()
:
客户端调用
gen_server:start_link()
至少指定定义了 handle_call/handle_cast 函数的模块。客户端调用
gen_server:call(ServerName, Request)
,一般封装在接口函数中gen_server:call(ServerName, Request) 被定义为
send a message to the server
,像这样:ServerName ! {request, Request, call, self(), ModName}.
ModName 之前绑定到在 gen_server:start_link() 中指定的原子:第二个参数是您指定包含函数定义的模块名称的位置 handle_call(),handle_cast()等
当服务器收到该消息时,服务器调用
ModName:handle_call()
,并且您的 ModName:handle_call() 代码对请求执行某些操作:handle_call(Request, ClientPid, ServerLoopValue) -> %%Compute some result using information in Request/ServerLoopValue
您的 ModName:handle_call() 函数的最后一行告诉服务器将什么发送回客户端
a response
:{Response, NewServerLoopValue}.
然后服务器会做这样的事情:
From ! {reply, Response, ServerPid}. loop(NewServerLoopValue).
并且 NewServerLoopValue 成为服务器循环 () 的新 全局 变量。每个服务器都有一个 loop() 函数,看起来像这样:
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 以供其他请求处理程序将来使用。
进入 gen_server 后门的流量使用 {active, true}, {packet, 4}
:
客户调用
gen_tcp:send()
.在套接字的服务器端,Erlang从套接字中读取数据,构造消息元组,并将消息元组放入
server's mailbox
。服务器从邮箱中获取{tcp, ...}消息并调用
handle_info()
.handle_info() 调用
gen_tcp:send(Socket, Response)
将响应发送回客户端。最后一行handle_info()告诉服务器在调用服务器的loop()函数时使用什么值:
{noreply, SomeValue} => loop(SomeValue)
进入 gen_server 后门的流量使用 {active, false}, {packet, 0}
的 TCP 套接字: