I am in early stages of learning Erlang and I need some further assistance
- Look at some simple, non gen_server client-server examples. Try to come up with a simple idea for your own client-server and write the code.
- Learn about parameterizing a simple server with a module name.
- Learn about gen_server and behaviors.
- Practice converting a simple server to a gen_server. Using a text editor with a split window is really handy.
- Learn about gen_tcp and sockets.
- Look at examples that combine gen_tcp and sockets with gen_server.
See here:
http://erlang.org/doc/design_principles/des_princ.html
I would not start at step 6, which you seem to be doing.
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?
No callback. TCP data bypasses the whole gen_server architecture. TCP data comes in through the back door, so to speak, along with other intruders. So gen_server:handle_info() is there to deal with them. handle_info() checks the servers mailbox for any messages that match the pattern specified as the argument to handle_info().
Anything that needs to be done to the TCP data is done inside handle_info(). Of course, if you need to do some complicated data processing in handle_info(), you can always call helper functions to calculate some result:
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. |
+------------------------------------------------------------------------------------+
The flow when a client calls 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.
The flow going in the back door of a gen_server using a TCP socket with {active, true}, {packet, 4}
:
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)
The flow going in the back door of a gen_server using a TCP socket with {active, false}, {packet, 0}
:
Erlang gen_tcp not receiving anything