我正处于学习 Erlang 的早期阶段,我需要一些进一步的帮助
- 看一些简单的非 gen_server 客户端-服务器示例。尝试为您自己的客户端-服务器想出一个简单的想法并编写代码。
- 了解如何使用模块名称参数化简单服务器。
- 了解 gen_server 和行为。
- 练习将简单服务器转换为 gen_server。使用带有拆分窗口的文本编辑器非常方便。
- 了解 gen_tcp 和套接字。
- 查看将 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()时的流程:
客户端调用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() 函数,看起来像这样:
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 就像一个 global 变量,所有不同的请求都可以看到。各种 gen_server 请求处理程序可以使用存储在 ServerLoopValue 中的信息来计算响应,或者它们可以将信息添加到其他请求处理程序将来可以使用的 ServerLoopValue。
使用带有{active, true}, {packet, 4} 的 TCP 套接字进入 gen_server 后门的流:
客户端调用gen_tcp:send()。
在服务器端的socket,Erlang从socket中读取数据,构造一个消息元组,将消息元组放入server's mailbox。
服务器从邮箱中检索{tcp, ...}消息并调用handle_info()。
handle_info() 调用gen_tcp:send(Socket, Response) 将响应发送回客户端。
-
handle_info() 的最后一行告诉服务器在调用服务器的 loop() 函数时使用什么值:
{noreply, SomeValue} => loop(SomeValue)
使用带有{active, false}, {packet, 0} 的 TCP 套接字进入 gen_server 后门的流:
Erlang gen_tcp not receiving anything