我正在学习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};
computerInGermanyProcess(RawData) ->
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()
时的流程:
Client calls gen_server:start_link()
which at a minimum specifies the module in which the handle_call/handle_cast functions are defined.
Client calls gen_server:call(ServerName, Request)
, which is usually wrapped in an interface function.
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.
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
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后门的流程:
Client calls gen_tcp:send()
.
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
.
The server retrieves the {tcp, ...} message from the mailbox and calls handle_info()
.
handle_info() calls gen_tcp:send(Socket, Response)
to send a response back to the client.
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的解释。