1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
-module(idcs).
-author('Andrew Yu <andrew@andrewyu.org>').
-behavior(gen_server).
-export([init/1, code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-export([accept_loop/1]).
-export([start/3]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
-record(server_state, {
port,
loop,
ip=any,
lsocket=null}).
start(Name, Port, Loop) ->
State = #server_state{port = Port, loop = Loop},
gen_server:start_link({local, Name}, ?MODULE, State, []).
init(State = #server_state{port=Port}) ->
case gen_tcp:listen(Port, ?TCP_OPTIONS) of
{ok, LSocket} ->
NewState = State#server_state{lsocket = LSocket},
{ok, accept(NewState)};
{error, Reason} ->
{stop, Reason}
end.
handle_cast({accepted, _Pid}, State=#server_state{}) ->
{noreply, accept(State)}.
accept_loop({Server, LSocket, {M, F}}) ->
{ok, Socket} = gen_tcp:accept(LSocket),
% Let the server spawn a new process and replace this loop
% with the echo loop, to avoid blocking
gen_server:cast(Server, {accepted, self()}),
M:F(Socket).
% To be more robust we should be using spawn_link and trapping exits
accept(State = #server_state{lsocket=LSocket, loop = Loop}) ->
proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket, Loop}]),
State.
% These are just here to suppress warnings.
handle_call(_Msg, _Caller, State) -> {noreply, State}.
handle_info(_Msg, Library) -> {noreply, Library}.
terminate(_Reason, _Library) -> ok.
code_change(_OldVersion, Library, _Extra) -> {ok, Library}.
|