summaryrefslogtreecommitdiff
path: root/server.erl
blob: 7fcd6ab5148ec68c5e270832adbe51a32e95e122 (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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
-module(server).
-export([server/0, server/2, worker/2, create_workers/2, acceptor/2, listener/1, manager/1, broadcast/2, broadcast/3, handle_data/3]).

server() ->
    server(1235, 10).
server(Port, NumWorkers) ->
    ets:new(client_table, [named_table, public, {read_concurrency, true}]),
    ets:insert(client_table, {counter, 0}),
    ets:insert(client_table, {server, "Server"}),

    {ok, ServerSock} = gen_tcp:listen(Port, [list]),
    spawn(?MODULE, create_workers, [NumWorkers, self()]),
    supervisor(ServerSock, []).

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

supervisor(ServerSock, Pids) ->
    receive
        worker_new ->
            {NewPid, _} = spawn_monitor(?MODULE, worker, [ServerSock, Pids]),
            io:format("Creating new worker ~p\n", [NewPid]),

            message_all(Pids, {ping, NewPid}),
            supervisor(ServerSock, [NewPid | Pids]);

        {'DOWN', _, process, From, Reason} ->
            io:format("Worker ~p exited due to ~p\n", [From, Reason]),
            self() ! create,

            message_all(lists:delete(From, Pids), {close, From}),
            supervisor(ServerSock, lists:delete(From, Pids));
        {announce, Data} ->
            [Pid|_] = Pids,
            Pid ! {broadcast, "Server", ok, Data},
            supervisor(ServerSock, Pids)
    end.

message_all([Pid|Pids], Data) ->
    Pid ! Data,
    message_all(Pids, Data);
message_all([], _) -> ok.

worker(ServerSock, Pids) ->
    process_flag(trap_exit, false),
    ListenerPid = spawn_link(?MODULE, listener, [self()]),
    spawn_link(?MODULE, acceptor, [ListenerPid, ServerSock]),
    manager(Pids).

manager(Socks) ->
    receive
        {ping, Sock} ->
            manager([Sock | Socks]);
        {close, Sock} -> 
            manager(lists:delete(Sock, Socks));

        {repeat, Data} ->
            broadcast(Socks, Data, norepeat),
            manager(Socks);
        {broadcast, FromIden, FromSock, Data} ->
            NewData = FromIden ++ ": " ++ Data,
            broadcast(lists:delete(FromSock, Socks), NewData),
            manager(Socks);
        {pm, FromIden, ToSock, Data} ->
            NewData = "(pm) " ++ FromIden ++ ": " ++ Data,
            broadcast([ToSock], NewData),
            manager(Socks)
    end.

listener(ManagerPid) ->
    receive
        {tcp, Sock, Data} ->
            spawn(?MODULE, handle_data, [ManagerPid, Sock, Data]),
            listener(ManagerPid);
        {tcp_opened, Sock} ->
            ManagerPid ! {ping, Sock},
            client_add(Sock),
            listener(ManagerPid);
        {tcp_closed, Sock} -> 
            ManagerPid ! {close, Sock},
            client_del(Sock),
            listener(ManagerPid)
    end.

acceptor(ListenerPid, ServerSock) ->
    case gen_tcp:accept(ServerSock) of
        {ok, Sock} ->
            ok = gen_tcp:controlling_process(Sock, ListenerPid),
            ListenerPid ! {tcp_opened, Sock},
            acceptor(ListenerPid, ServerSock)
    end.


handle_data(ManagerPid, From, Data) ->
    case catch parse_data(ManagerPid, From, Data) of
        ok -> ok;
        _ -> 
            ManagerPid ! {pm, "Server", From, "ERR: Transaction Failed\n"}
    end.

parse_data(ManagerPid, From, Data) ->
    case string:tokens(Data, " ") of
        ["/pm" | [ToIden | _]] ->
            NewData = string:slice(Data, string:length(ToIden) + 5),
            ManagerPid ! {pm, client_iden(From), client_sock(ToIden), NewData};
        ["\n"] -> ok;
        _ -> 
            ManagerPid ! {broadcast, client_iden(From), From, Data}
    end, ok.
    

broadcast(Socks, Data) ->
    broadcast(Socks, Data, ok).
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.

client_add(Sock) ->
    [{_, Count}|_] = ets:lookup(client_table, counter),
    Iden = "Anon"++integer_to_list(Count),
    ets:insert(client_table, {Iden, Sock}),
    ets:insert(client_table, {Sock, Iden}),
    ets:update_counter(client_table, counter, 1).
client_del(Sock) ->
    [{_, Iden}|_] = ets:lookup(client_table, Sock),
    ets:delete(client_table, Sock),
    ets:delete(client_table, Iden).
client_iden(Sock) ->
    [{_, Iden}|_] = ets:lookup(client_table, Sock),
    Iden.
client_sock(Iden) ->
    [{_, Sock}|_] = ets:lookup(client_table, Iden),
    Sock.