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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
-module(server).
-export([server/2, worker/2, create_workers/2, acceptor/2, listener/1, manager/1, broadcast/2, broadcast/3, handle_data/3]).
server(Port, NumWorkers) ->
ets:new(client_table, [named_table, public, {read_concurrency, true}]),
ets:insert(client_table, {counter, 0}),
{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} ->
broadcast(lists:delete(FromSock, Socks),
io_lib:format("~s: ~s\n", [FromIden, Data])),
manager(Socks);
{pm, FromIden, ToSock, Data} ->
broadcast([ToSock],
io_lib:format("(pm) ~s: ~s\n", [FromIden, Data])),
manager(Socks);
{rename, _, OldIden, NewIden } ->
self() ! {broadcast, "Server", ok,
io_lib:format("~s renamed to ~s", [OldIden, NewIden])},
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, string:trim(Data)) of
ok -> ok;
_ ->
ManagerPid ! {pm, "Server", From, "ERR: Transaction Failed"}
end.
parse_data(ManagerPid, From, Data) ->
case string:tokens(Data, " ") of
["/help" | _] ->
ManagerPid ! {pm, "Server", From, "..."};
["/pm" | [ToIden | _]] ->
NewData = string:slice(Data, string:length(ToIden) + 5),
ManagerPid ! {pm, client_iden(From), client_sock(ToIden), NewData};
["/pm" | _] ->
ManagerPid ! {pm, "Server", From, "/pm: ..."};
["/rename" | [NewIden | []]] ->
{OldIden, _} = client_update(From, NewIden),
ManagerPid ! {rename, From, OldIden, NewIden};
["/rename" | _] ->
ManagerPid ! {pm, "Server", From, "/rename: ..."};
[[$/ | _] | _] ->
ManagerPid ! {pm, "Server", From,
"Invalid Command, use /help for help"};
[] -> 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:update_counter(client_table, counter, 1),
Iden = "Mitko"++integer_to_list(Count),
true = ets:insert_new(client_table, {Iden, Sock}),
true = ets:insert_new(client_table, {Sock, Iden}),
Iden.
client_del(Sock) ->
[{_, Iden}|_] = ets:lookup(client_table, Sock),
ets:delete(client_table, Sock),
ets:delete(client_table, Iden),
Iden.
client_update(Sock, NewIden) ->
[{_, OldIden}|_] = ets:lookup(client_table, Sock),
ets:delete(client_table, OldIden),
[] = ets:lookup(client_table, NewIden),
true = ets:update_element(client_table, Sock, {2, NewIden}),
true = ets:insert_new(client_table, {NewIden, Sock}),
{OldIden, NewIden}.
client_iden(Sock) ->
[{_, Iden}|_] = ets:lookup(client_table, Sock),
Iden.
client_sock(Iden) ->
[{_, Sock}|_] = ets:lookup(client_table, Iden),
Sock.
|