2
votes

I'm pretty new to Erlang, however I've been trying to create a simple client server implementation. The server would hypothetically create worker processes to do "heavy work" in a "database" and then return the calculated values to the client.

My current steps are:

  1. Create a server process.
  2. Spawn a worker.
  3. Send the worker some work to do based off the clients input. (Here's where I'm confused)
  4. Send the data from the worker back to the client.

Here is some sample code.

-module(server).
-compile(export_all).

server() ->
    receive
        {From, {client, Name}} ->
            io:format("Server has received request for ~p from ~p~n", [Name, From]),
            Worker = spawn(server, getNameFromDataBase(self()),[]),
            Worker ! Name,
            From ! LastName,%%data returned from worker
            server();
        {database, LastName} ->
            Data = LastName,
            server()
    end.


getNameFromDataBase(Server_Address) ->
    receive
        {name, Name} ->
            timer:sleep(5000), %doing difficult work!
            Server_Address ! {database, "Johnson"}
    end.



client(Server_Address) ->
    Server_Address ! {self(), {client, "Jim"}},
    receive
            {server, LastName} ->
                io:format("Server got person's last name~p~n", LastName)
    end.

How do you make it so that the data you receive from the worker will be seen and be able to be sent to the client?

2

2 Answers

3
votes

I can see you have two main problems on the server process:

  1. Sending message from the server to the database worker.
  2. Sending message that the server received from worker back to the client.

Sending message to the worker

Your following lines of code from L8 - L10 is incorrect.

%% ...
Worker = spawn(server, getNameFromDataBase(self()), []),
Worker ! Name,                                           
From ! LastName,                   
%% ...

To spawn a function as a process, you must provide any argument(s) to the function as members of the list in the third argument of spawn/3, not call the function directly.

Worker = spawn(server, getNameFromDataBase, [self()]),

Sending only Name to Worker will go unnoticed since getNameFromDataBase expects {name, Name}. This should be changed to

Worker ! {name, Name}

Sending result (LastName) to the client

It doesn't make sense to send From ! LastName on L10 since you have not received LastName. This should be moved to the second match expression when the server have received {database, LastName} from the worker. Additionally, the client expects {server, LastName}, not LastName. So it was supposed to be From ! {server, LastName}.

However, there's a problem. You cannot get access to From in that scope because it was never defined.

%% ...
{database, LastName} ->
    From ! {server, LastName},      % `From` is not defined
    server()
%% ...

What you may want to do without having to change the message tuple is to define another server/1 function like so:

server(ClientPID) ->
    receive
        {_, LastName} ->
            ClientPID ! {server, LastName},
            server()
    end.

and call this from server/0:

server() ->
    receive
        {From, {client, Name}} ->
            io:format("Server has received request for ~p from ~p~n", [Name, From]),
            Worker = spawn(?MODULE, getNameFromDataBase, [self()]),
            Worker ! {name, Name},
            server(From)      % call `server/1` with the client's PID
    end.

Finally, to wrap everything up, if you haven't already, create an initiating function to spawn the server and client processes. Here is the completed code:

server() ->
    receive
        {From, {client, Name}} ->
            io:format("Server has received request for ~p from ~p~n", [Name, From]),
            Worker = spawn(?MODULE, getNameFromDataBase, [self()]),
            Worker ! {name, Name},
            server(From)
    end.

server(ClientPID) ->
    receive
        {_, LastName} ->
            ClientPID ! {server, LastName},
            server()
    end.

getNameFromDataBase(ServerAddr) ->
    receive
        {name, Name} ->
            io:format("worker received ~p~n",[Name]),
            timer:sleep(5000),
            ServerAddr ! {database, "Johnson"}
    end.

client(ServerPID) ->
    ServerPID ! {self(),{client, "Jim"}},
    receive
        {server, LastName} ->
            io:format("Client got person's last name ~p from Server ~n", [LastName])
    end.

run() ->
    ServerPID = spawn(?MODULE, server, []),
    spawn(?MODULE, client, [ServerPID]).

Last tip: Do not use export_all compile option. Only export the necessary functions for good encapsulation.

1
votes

You'll get the message from the worker in the {database, LastName} -> branch of the receive. Just include the information you need to handle it in the message itself:

server() ->
    receive
        {From, {client, Name}} ->
            io:format("Server has received request for ~p from ~p~n", [Name, From]),
            Worker = spawn(server, getNameFromDataBase, [self()]),
            Worker ! {name, From, Name},
            server();
        {database, From, LastName} ->
            From ! LastName,
            server()
    end.

getNameFromDataBase(Server_Address) ->
    receive
        {name, From, Name} ->
            timer:sleep(5000), %doing difficult work!
            Server_Address ! {database, From, "Johnson"}
    end.

Note that the arguments need to be passed in the list (third argument of spawn). Also if you create the worker just to send a single message immediately you can just as well pass its content as arguments; I didn't make this change here to make it easier to see the main change.

Alternately, you can store the From corresponding to Worker in server state (argument to server()).