Saturday, March 8, 2008

Yet another simple server in erlang, also a look at use of eunit test framework



The diagram above depicts my simple architecture for an Erlang server and library that responds to various client character requests. You should already be familiar with some of Erlang's advantages and know that the strength of Erlang is its lightweight parallel process/thread management and network programming. Not to mention, it is not a bad easy to learn, general purpose functional programming language.

Starting the server:


The following set of instructions are used to start the server and two processes are created. The first is a simple one that waits for incoming requests from the server middleware piece.

%% Simple functional test for the server library
simple_server_lib() ->
io:format("starting server <with server library support>~n"),
AppHandler = start(),
io:format("trace: top-level app handler~p~n", [AppHandler]),
Client = #irc_server_info{app_handler=AppHandler},
ServStart = server_lib:start_link(Client),
case ServStart of
{ ok, P } ->
io:format("trace: app:server pid/lib [~p]~n", [P]),
server_lib:server_listen(P),
State = server_lib:get_cur_state(P),
io:format("trace: state [~p]~n", [State]),
server_handler(#serv_handler_info{lib_handler=P}, idle)
end.


The simple wait for messages process



wait_for_messages(idle) ->
io:format("trace: app:waiting for messages~n"),
receive
{ connection_closed } ->
io:format("trace: app: client connection closed~n");
{ shutdown } ->
io:format("trace: app: shutting down server.~n"),
% Exit point for the application, full shutdown.
erlang:halt();
Else ->
io:format("trace: app: invalid message~n")
end.

The server library



handle_call(irc_server_bind, _From,
#server_state{client=Client } = State) ->
Port = Client#irc_server_info.port,
io:format("trace: lib:handle_call:bind Port:<~p>~n", [Port]),
{ok, ServSock} = server_bind(Port),
{reply, ok, State#server_state{serv_sock=ServSock, state=connecting}};
handle_call(irc_accept_clients, _From,
#server_state{serv_sock=ServSock, app_handler=AppHandler } = State) ->

io:format("trace: lib:handle_call accept_clients. [~p]~n", [AppHandler]),
ClientSock = server_accept(ServSock),
%***************
% Start the handler process
%***************
io:format("trace: [!!] lib:handle_call client-socket: [~p]~n", [ClientSock]),
% Pass the main app handler, serv lib handler, client sock
% And launch the handler process.
ClientInfo = #client_info{app_handler=AppHandler,serv_lib=self(),
client_sock=ClientSock},
{ ok, ClientServ } = client_handler:start_link(ClientInfo),
% Assign a new controlling process.
gen_tcp:controlling_process(ClientSock, ClientServ),
% Active the socket so that the client handler can handle the
% tcp data.
inet:setopts(ClientSock, [{packet, 0}, binary,
{nodelay, true},{active, true}]),
{reply, ok, State};
handle_call(get_cur_state, _From, #server_state{} = State) ->
% Generic method to get the current state.
io:format("trace: lib:handle_call:get_cur_state~n"),
{reply, {ok, State}, State}.

The Client Handler



handle_info({tcp, Sock, Data}, State) ->
inet:setopts(Sock, [{active, once}]),
io:format("trace: lib:info.tcp data [~p]~n", [Data]),
{Prefix, Command, Args} =
try data_lib:scan_string(Data)
catch
_:X ->
io:format("ERROR: error attempting to parse input command error:[~p]~n",[X]),
{"", (Data), ""}
end,
% Invoke the client data handler to process incoming messages.
HandleState=client_handle_data(State, {Prefix, Command, Args}),
{noreply, State};
handle_info({tcp_closed, Sock}, State) ->
%*************************
% Client has closed the connection.
%*************************
io:format("trace: lib:info.tcp_closed state:[~p]~n", [State]),
AppHandler = State#client_state.app_handler,
inet:setopts(Sock, [{active, once}]),
gen_tcp:close(Sock),
% Send a request to the handler; we lost a connection.
AppHandler ! {connection_closed},
{noreply, State#client_state{state=disconn, client_sock=nil}};
handle_info({tcp_error, Sock, Reason}, State) ->
io:format("trace: lib:info.tcp_error~n"),
inet:setopts(Sock, [{active, once}]),
{noreply, State#client_state{state=disconn}};
handle_info(Msg, State) ->
% Generic handle info handler
io:format("trace: lib:info.<generic> [~p] [~p]~n", [Msg,State]),
{noreply, State}.

Running the Application



Full Source (through subversion)

http://openbotlist.googlecode.com/svn/trunk/botlistprojects/laughingman/test/ircserver

No comments: