summaryrefslogtreecommitdiff
path: root/server.erl
blob: dbf418263247fa97568e9b62a3d7e0384fa60b9e (plain)
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
-module(server).
-export([server/2, manager/1, broadcast/2, broadcast/3, worker/2, create_workers/2]).

server(Port, NumWorkers) ->
    process_flag(trap_exit, true),
    {ok, ServerSock} = gen_tcp:listen(Port, [list]),
    spawn(?MODULE, create_workers, [NumWorkers, self()]),
    supervisor(ServerSock, []).

create_workers(0, _) -> ok;
create_workers(Num, Supervisor) ->
    Supervisor ! create,
    create_workers(Num - 1, Supervisor).

supervisor(ServerSock, Pids) ->
    receive
        create ->
            io:format("Creating new worker\n"),
            NewPid = spawn_link(?MODULE, worker, [ServerSock, Pids]),
            ping_all(Pids, NewPid),
            supervisor(ServerSock, [NewPid | Pids]);
        {'EXIT', From, Reason} ->
            io:format("~p exited due to ~p\n", [From, Reason]),
            self() ! create,
            supervisor(ServerSock, lists:delete(From, Pids))
    end.

ping_all([Pid|Pids], NewPid) ->
    Pid ! { ping, NewPid },
    ping_all(Pids, NewPid);
ping_all([], _) -> ok.

worker(ServerSock, Pids) ->
    process_flag(trap_exit, false),
    ManagerPid = self(),
    spawn_link(
        fun() ->
           process_flag(trap_exit, false),
           listener(ManagerPid, ServerSock)
        end),
    manager(Pids).

listener(ManagerPid, ServerSock) ->
    case gen_tcp:accept(ServerSock) of
        {ok, Sock} ->
            ok = gen_tcp:controlling_process(Sock, ManagerPid),
            ManagerPid ! {ping, Sock},
            listener(ManagerPid, ServerSock);
        Other ->
            exit(Other)
    end.

manager(Socks) ->
    receive
        {tcp, Sock, Data} ->
            broadcast(lists:delete(Sock, Socks), Data),
            manager(Socks);
        {tcp_closed, Sock} -> 
            manager(lists:delete(Sock, Socks));
        {ping, Sock} ->
            manager([Sock | Socks]);

        {repeat, Data} ->
            broadcast(Socks, Data, norepeat),
            manager(Socks);
        Other ->
            exit(Other)
    end.

broadcast([Pid|Socks], Data, norepeat) when is_pid(Pid) ->
    broadcast(Socks, Data, norepeat);
broadcast([Pid|Socks], Data, Opt) when is_pid(Pid) ->
    Pid ! {repeat, Data},
    broadcast(Socks, Data, Opt);

broadcast([Sock|Socks], Data, Opt)  ->
    gen_tcp:send(Sock, Data),
    broadcast(Socks, Data, Opt);
broadcast([], _, _) -> ok.

broadcast(Socks, Data) ->
    broadcast(Socks, Data, ok).