Lab Exercise
In your home directory create a file named erlang.cookie In this file store a keyword you'll use to enable shared communications between otter and the pups (any unique name is fine) This example assumes the sender is running on pup1 and the receiver is running on otter. Store the file as comms.erl, then compile it using "erlc comms.erl" Open a window on otter, and start erl using erl -sname talkRecver In erl run comms:start_recv(). Open a window on pup1, and start erl using erl -sname talkSender In erl run comms:start_send(). You should see a series of messages/acknowledgements passed between the two. |
-module(comms). -export([sender/1, start_send/0, start_recv/0, recv/1]). % create a macro that allows us to use ?ELSE as % a catch-all for if/case statements -define(ELSE, true). % sends an initial greeting message % then waits for the acknowledgement sender(SendMsg) -> { recvid, talkRecver@otter } ! SendMsg, io:format("greeting sent ~n"), receive ack -> io:format("send got greeting ack ~n"), sender(normmsg); normack -> io:format("send got message ack ~n"), sender(byebye); byeack -> io:format("send got hangup ack ~n"); ?ELSE -> io:format("send got unknown msg ~n") end. % waits for an incoming message and sends an % appropriate response (possibly calling itself % again to wait for subsequent messages recv(RespMsg) -> receive greet -> io:format("recv got greeting ~n"), { sendid, talkSender@pup1 } ! RespMsg, recv(normack); normmsg -> io:format("recv got message ~n"), { sendid, talkSender@otter } ! RespMsg, recv(normack); byebye -> io:format("recv got hangup ~n"), { sendid, talkSender@otter } ! byeack; ?ELSE -> io:format("recv got unknown msg ~n") end. start_recv() -> RECVID = spawn(comms, recv, [ ack ]), register(recvid, RECVID), io:format("receiver ~w ~n", [RECVID]). start_send() -> SENDID = spawn(comms, sender, [ greet ]), register(sendid, SENDID), io:format("sender ~w ~n", [SENDID]). |
Note that you can empty the message queue for a process using flush(). Note also that the 'human readable' form of PIDs is |
EXERCISE 1.1 (i) Have the sending user enter the id (e.g. talkRecver@otter) of the person they wish to communicate with, and a block of text to be sent. (ii) Have the recv operation display the message received and who it came from. (iii) Make the receives non-blocking by adding a timeout to the receive statement. This is done by structuring your receives like: receive pattern1 -> pattern 1 actions; ... patternN -> pattern N actions; true -> default actions after 5000 -> timeout actions, taken after 5000 milliseconds end. |
The calc module shown below provides a client server model for a simple calculator. Compile the module then start erl with erl -sname server@localhost and to start the server use calc:start(). Start up a client in another window with erl -sname client@localhost then have the client node connect to the server node with net_kernel:connect_node(server@localhost). You can then use the client window to run commands on the server, i.e. calc:addi(N). calc:divi(N). calc:subt(N). calc:times(N). The server-side calculator starts with an initial value of 0, and the operations above add, divide, subtract, or multiply the currently stored value by N (which can be any integer). The result of the operation is returned to the client, and the internal state of the calculator is updated. To stop the server run calc:stop(). |
% calc.erl -module(calc). % will use gen_server as a template for the behaviour of our calculator -behaviour(gen_server). % gen_server requires we implement the following routines -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -export([code_change/3, start/0, stop/0]). % for our calculator we will supply add, subt, times, and divi -export([addi/1, times/1, divi/1, subt/1]). % instruct gen_server how to process calc:start(), % which is what the user must run to initiate the calculator start()->gen_server:start_link({global, ?MODULE}, ?MODULE, [], []). %******* %** Aside: %** The use of the { global, ?MODULE } registers %** your module name globally in much the same way %** a register command would. %** If you want to see a list of registered names, enter %** global:registered_names(). %******* % instruct gen_server how to process calc:stop(), % which is what the user must run to shut down the calculator stop() -> gen_server:cast( { global, ?MODULE }, stop). % initially the step is 0 and the value held in the calculator is 0 init([]) -> {ok, {0, 0}}. % instruct gen_server how to process calls to addi, subt, divi, and times addi(Num) -> gen_server:call( { global, ?MODULE }, {addi, Num}). times(Num) -> gen_server:call( { global, ?MODULE }, {times, Num}). subt(Num) -> gen_server:call( { global, ?MODULE }, {subt, Num}). divi(Num) -> gen_server:call( { global, ?MODULE }, {divi, Num}). % the calculator stores it's current { step, value }, % starting each at 0 % each call to addi(X), subt(X), divi(X), times(X) increments step % and uses X and the old calculator value as the operands % the returned value is reply, answer, updated calculator state handle_call({addi, Num}, _From, { Step, Value }) -> {reply, Value+Num, { Step+1, Value+Num} }; handle_call({times, Num}, _From, { Step, Value }) -> {reply, Value*Num, { Step+1, Value*Num} }; handle_call({subt, Num}, _From, { Step, Value }) -> {reply, Value-Num, { Step+1, Value-Num} }; handle_call({divi, Num}, _From, { Step, Value }) -> {reply, Value/Num, { Step+1, Value/Num} }; % the start() op begins the calculator with state { step 0, value 0 } handle_call({start}, _From, _State) -> { reply, 0, { 0, 0 } }. % instruct gen_server how to handle a stop request handle_cast(stop, State) -> { stop, normal, State }. handle_info(_Msg, State) -> { noreply, State }. % behaviour to execute on calculator shutdown terminate(_Reason, _State) -> ok. % used if we want to hot-swap calculator code (not done here) code_change(_OldVersion, State, _Extra) -> { ok, State }. |
EXERCISE 1.2 (i) Add a calc:reset() option that resets the stored value to 0 and resets the step number to 0. (ii) Add a list to the tuple containing the server's state, and have the list record each of the operations performed. (iii) Have the terminate command print out a list of all operations performed, in order, since the server was started. |
Additional Resources