Home > Erlang > Ping – Pong example in Erlang 2

Ping – Pong example in Erlang 2


Eccomi di nuovo qui, per parlarvi del mio linguaggio preferito!

L’ultima volta mi sono interrotto bruscamente a causa della lunghezza del post che stava diventando insostenibile pure per me..figuariamoci per un povero lettore poi!

Se non sbaglio si stava parlando di server, client e di ping pong; dunque quest’oggi non farò altro che proseguire con l’argomento, introducendo una seconda possibile implementazione basata sul gen_server behaviour offerto da erlang.

Il behaviour module gen_server è un utile punto di partenza per implementare un server; un generico processo (gen_server) implementato usando questo behaviour avrà un set standard di funzioni d’interfaccia e fornirà funzionalità per evidenziare e riportare errori. Inoltre è facile inserire tale tipo di modulo in un OTP supervision tree.

Un modulo implementato su un gen_server behaviour mette a disposizione una serie di cosidette callbacks functions, che aiutano lo sviluppatore nel corso dello sviluppo e che gli permettono di scrivere molto meno codice.

Possiamo pensare che dietro tale behaviour ci sia una funzione di loop ad un parametro (detto state). Tale funzione loop/1 è invisibile all’utente, che ha però a disposizione delle funzionalità per accedere a tale stato (ottenendone il valore attuale, o modificandolo).

Se come me utilizzate emacs con l’erlang mode, potete ottenere una versione molto semplice di gen_server semplicemente andando all voce tools -> skeletons -> gen_server nel menù a tendina, vi appariranno sul vostro buffer tutte le funzioni base con rispettivi commenti che io ho tolto per non appesantire il codice. Piu o meno avrete qualcosa di simile:

-module(server).

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-record(state, {}).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, #state{}}.

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

star_link/0 è la funzione che fa partire il processo e che lo registra (se specificate un nome). Tale funzione andrebbe sempre chiamata dal supervisor, ed a sua volta chiama la funzione init del modulo (potete specificare dei parametri se la vostra init li necessita).

La funzione init/0 nel nostro caso viene chiamata da start_link e se il processo è stato inizializzato nel modo corretto ritorna la tupla {ok, #state} dove il secondo elemento rappresenta lo stato interno del processo. In realtà in init andrebbero eseguite tutte le operazioni di inizializzazione del processo (ad esempio se intendeve creare delle tables in mnesia dovreste farlo qui). La funzione terminate/2 rappresenta l’opposto di init: tale funzione viene chiamata ogni qualvolta il gen_server sta per terminare appunto e al suo interno dovrebbero essere specificate tutte le funzioni rivolte al clean-up. In alcuni casi terminate tuttavia non viene chiamata, vi suggerisco di vedere questo link per maggiori informazioni a riguardo.

Le funzioni handle_call/3, handle_cast/2 ed handle_info/2 sono invece i veri e propri accessi allo state del nostro processo.  La prima si occupa di gestire le richieste al server in modo sincrono e fornisce come risultato una tupla di forma {reply, Reply, State} dove Reply è il valore spedito al richiedente e State è il nuovo state assunto dal loop interno. La seconda opera in modo simile, ma si occupa delle richieste asincrone, indipendentemente dal risultato della chiamata ritorna sempre ok. La teza invece viene invocata quando la richiesta non è nè di tipo sincrono nè asincrono oppure quando sorge un timeout error.

Per spiegarne il funzionamento passo ad implementare il nostro server di ping pong (implemento solo handle_call/3):

-module(server).

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-record(state, {times}).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, #state{times=0}}.

handle_call({ping, 0}, _From, _State) ->
    Reply = {pong, 0},
    NewState = #state{times = 0},
    {reply, Reply, NewState};

handle_call({ping, N}, _From, _State) ->
    Reply = {pong, N-1},
    NewState = #state{times = N-1},
    {reply, Reply, NewState};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

Come vedete ho iniziato cambiando il record su cui è basato lo status: in questo caso è semplicemente un valore detto times, quando il server viene inizializzato tale valore è a 0. Sono state aggiunte due nuove dichiarazioni per handle_call/3 (mi raccomando che quella generica va alla fine, state attenti pure ai punti e virgola!): la prima riguarda ogni richiesta sincrona rappresentata dalla tupla {ping, 0} mentre la seconda le richieste del tipo {ping, N}.

Ogniqualvolta arriva al server una chiamata del primo tipo (tramite la funzione gen_server:call(ServerRef, Request) (dove ServerRef è il riferimento al nostro server e Request la richiesta),n settiamo il valore di ritorno a {pong, 0} e cambiamo il valore dello stato interno a 0. Se arriva invece al server una chiamata di tipo {ping, N}, rispondiamo con un {ping, N-1} ed aggiorniamo il valore dello stato interno a N-1.

Il client è molto facile da implementare in questo caso. Ci serve una sola funzione ping/1 che esegue gen_server:call/2; ovviamente in questo caso il primo paramentro sarà il nome con cui è registrato il nostro server. Il risultato di tale chiamata verrà sottoposto ad un pattern matching tramite il quale la nuova operazione da compiere verrà stabilita.

-module(client).
-export([ping/1]).

ping(Times) ->
    case gen_server:call(server, {ping, Times}) of
	{pong, 0} ->
	    io:format("pong ~n", []);
	{pong, TimesLeft} ->
	    io:format("pong ~n", []),
	    ping(TimesLeft)
    end.

Ed ecco il codice in esecuzione:

bellerofonte:blog pegaso$ erl
Erlang (BEAM) emulator version 5.6.5  [async-threads:0]
[kernel-poll:false]

Eshell V5.6.5  (abort with ^G)
1> server:start_link().
{ok,<0.33.0>}
2> client:ping(2).
pong
pong
ok
3>
Categories: Erlang Tags: , ,
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: