%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Miniature parallel server example %%% %%% This code shows a tiny parallel server which handles any number of incoming %%% concurrent requests. This code is not particularly useful, but it shows how %%% to deal with TCP/IP using parallel spawned processes which do tail recursion %%% and which clean up after themselves. %%% %%% This server accepts an incoming connection, reads everything it has to say, %%% waits for it to close, then spits what the socket said into the erlang %%% console. If you're testing with telnet, you'll need to manually kill your %%% telnet session before anything will happen in the console. Most telnets %%% have manual kill as ctrl-] , q, but check your local manual if that doesn't %%% work. You may have to set an IP address if you're running on shared hosting. %%% %%% To test in most unix consoles, %%% telnet 127.0.0.1:5677 %%% %%% To test in a Windows console, %%% telnet 127.0.0.1 5677 %%% %%% Remember, you're gonna have to kill telnet before you'll see anything in the %%% console. That's what you get for using my lame examples. -module(miniserv). -export([start/0, start/1, start/2, main_server_loop/1, do_receive_from_socket/2, single_client_process/1]). -author("John Haugeland (StoneCypher) - stonecypher@gmail.com"). -define(DEFAULT_PORT, 5677). %%% Why 5677? ... because I said so. Remember, ports 1023 and below are reserved for the system, so don't use them unless you know what you're doing. -define(DEFAULT_IP, {0,0,0,0}). %%% 0.0.0.0 means "default local IP." You may need to override this on some web hosting plans if you purchased an IP address. %%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% if you start() without a port or IP, call start(Port, IP) with the default port and IP. These are really %%% just convenience stubs for the real entrypoint below. You don't have to start with a port or IP; you just %%% get to if you want to. We write a single implementation of the real entrypoint, then implement the default %%% port and IP in terms of the full version, as if you'd chosen manually. start() -> start(?DEFAULT_PORT, ?DEFAULT_IP). start(Port) -> start(Port, ?DEFAULT_IP). %%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% This is the entrypoint for the server, equivalent to int main() for c/c++ programs. This should only be %%% invoked once. start() actually terminates almost immediately after starting a new process to handle the %%% listening socket, which is why you get the console back immediately, instead of seeming to hang while %%% waiting for input. start(Port, Ip) -> case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}, {ip, Ip}]) of %%% attempt to start a listening socket. switch on the result. { ok, LSock } -> spawn(?MODULE, main_server_loop, [LSock]), io:format("Server started.~n"); %%% if the result is {ok, ...}, match the second element to LSock and spawn server(LSock). Fail -> io:format("Failed! Reason:~n ~s~n", [lists:flatten(Fail)]) %%% match any other result than success to the variable Fail, then spit that to terminal and die end. %%% the server is either now started in a different process or has failed, so terminate start(). %%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% server() is the workhorse. first it blocks (as a zero-weight process, so who cares) on gen_tcp:accept() %%% while waiting for a client. once it gets a client, it spawns that client into a new single_client_process, %%% then tail recurses into itself. main_server_loop(LSock) -> { ok, Sock } = gen_tcp:accept(LSock), %%% Accept the client on the listening socket, and match the results to Sock. We assume success in this trivial example. spawn(?MODULE, single_client_process, [Sock]), %%% Create a new process to handle this client, using single_client_process as the handler, passing Sock as an argument. main_server_loop(LSock). %%% Tail recurse the main server loop (indefinate.) A better server would be able to terminate; this one goes until assasinated. %%% When I say "and match the results to sock," I mean that gen_tcp:accept() is returning { ok, SocketStuff } . %%% By matching it to { ok, Sock }, we put the socket stuff in Sock, which we then pass around. Matching means %%% taking a tuple and pattern-matching it to a different tuple. This is useful, because essentially everything %%% in erlang returns tuples, so that returns can be verbose and complex (you also see this a lot in PHP and %%% mozart/oz.) The matching behavior allows you to conveniently filter things and work with just parts of %%% the tuple in a syntax not terribly unlike PHP's list() and then a huge ugly switch. %%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% single_client_process creates a process to handle one client. in this way, it's relatively straightforward %%% to handle an indefinate client count. any time there's a client, just spawn one of these and you're good %%% to go. scp() takes the socket, and calls do_receive() on it, which does the actual work of reading client %%% data. once do_receive() is done, we know the client is gone; therefore we close the socket and spit the %%% results back to the console; finally this process terminates, since the workhorse server() is still running. %%% Notice that we're not switching for error handling on do_receive. That's because I wrote a naive do_receive %%% that always returns ok, even if something bad happened. Yay short examples. single_client_process(Sock) -> { ok, Bin } = do_receive_from_socket(Sock, []), %%% calls the routine that eats everything a socket has to say, starting with an empty list for data; match the results to Bin gen_tcp:close(Sock), %%% now that the socket transfer is done, close the socket (should already be closed by the client) io:format("Received: ~s~n", [Bin]). %%% print to the console Bin, the aggregated data from do_receive %%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% do_receive handles the actual scanning of the socket for data. this is also blocking, but since it's only %%% blocking the process created for single_client_process, and since that process has nothing to do until the %%% data is received, that's really just fine. the way this works is simple: scan the socket with gen_tcp:recv() %%% until the socket goes bye. Each time the socket has new data, append the NewData to the list we're passed in %%% Buffer and tail recurses into itself; eventually the client will run out of stuff to send, and will close, %%% at which point we return the results. do_receive_from_socket(Sock, Buffer) -> case gen_tcp:recv(Sock, 0) of { ok, NewData } -> do_receive_from_socket(Sock, [Buffer, NewData]); %%% Append and tail recurse. Remember, in erlang, [A,B] just concatenates lists A and B { error, closed } -> {ok, Buffer}; %%% if the socket is closed, return Buffer OtherError -> io:format("Error: ~s~n", [lists:flatten(OtherError)]), {ok, Buffer} %%% Some error other than a closed socket happened. Meh. Return the results anyway. end.