Files
rbx/x03-echo/echod.erl
T

249 lines
7.1 KiB
Erlang

%% @doc
%% super simple telnet echo server daemon
%%
%% no noise yet
%%
%% pimps and hos model (from trash talk template):
%%
%% ```
%% t_sup
%% |
%% t_clients
%% |
%% +--- t_client_man pimp
%% |
%% +--- t_client_sup bottom bitch
%% |
%% +--- <0.1.0> (t_client) ho
%% +--- <0.2.0> (t_client) ho
%% +--- <0.3.0> (t_client) ho
%% '''
%%
%% each ho:
%%
%% <ul>
%% <li>Takes listen socket as arg</li>
%% <li>spawns next ho</li>
%% </ul>
%%
%% ```
%% start(ListenSocket) ->
%% t_client_sup:start_acceptor(ListenSocket).
%%
%% start_link(ListenSocket) ->
%% proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).
%%
%% init(Parent, ListenSocket) ->
%% ok = io:format("~p Listening.~n", [self()]),
%% Debug = sys:debug_options([]),
%% ok = proc_lib:init_ack(Parent, {ok, self()}),
%% listen(Parent, Debug, ListenSocket).
%%
%% listen(Parent, Debug, ListenSocket) ->
%% case gen_tcp:accept(ListenSocket) of
%% {ok, Socket} ->
%% {ok, _} = start(ListenSocket),
%% {ok, Peer} = inet:peername(Socket),
%% ok = io:format("~p Connection accepted from: ~p~n", [self(), Peer]),
%% ok = t_client_man:enroll(),
%% State = #s{socket = Socket},
%% loop(Parent, Debug, State);
%% {error, closed} ->
%% ok = io:format("~p Retiring: Listen socket closed.~n", [self()]),
%% exit(normal)
%% end.
%% '''
%%
%% @end
-module(echod).
-include("./ansi.hrl").
-export([main/0]).
% ho from pimp pov
-record(pho,
{pid :: pid(),
ref :: reference(),
name :: string()}).
%% pimp state
-record(ps,
{lsock = none :: gen_tcp:socket(),
hos = [] :: [#pho{}],
ho_names = init_ho_names() :: [string()]}).
%% ho state
-record(hs,
{name = none :: string(),
daddy = none :: pid(),
asock = none :: gen_tcp:socket()}).
main() ->
io:format("RBX example 2 telnet echo server daemon~n"
"`one' part of the many-to-one relationship~n",
[]),
p_init().
% pimp process startup
p_init() ->
p_logln("pimp startup (p_init)"),
Port = 6969,
Opts = [binary, {packet, line}, {active, once}],
case gen_tcp:listen(Port, Opts) of
{ok, LSock} ->
ok = p_logfln("pimp starting: ~tp", [self()]),
PS_I = #ps{lsock = LSock},
PS_II = p_spawn_ho(PS_I),
p_loop(PS_II);
{error, closed} ->
%% dont be pimp
p_logln("listen socket closed")
end.
p_loop(PS = #ps{hos = Hos}) ->
receive
{ho, _HoPid, taken} ->
NewPS = p_spawn_ho(PS),
p_loop(NewPS);
{ho, HoPid, sent, Data} ->
HoName = p_ho_name(HoPid, PS),
SendMessage = io_lib:format("~ts :: ~ts\r\n", [HoName, Data]),
ok = p_echo_except(HoPid, HoName, SendMessage, Hos),
p_loop(PS);
Down = {'DOWN', _, _, _, _} ->
NewPS = p_ho_down(Down, PS),
p_loop(NewPS);
Unknown ->
ok = p_logfln("unknown message: ~tw", [Unknown]),
p_loop(PS)
end.
% given pid of ho, get her name
p_ho_name(HoPid, #ps{hos = Hos}) ->
case lists:keyfind(HoPid, #pho.pid, Hos) of
#pho{name = Name} -> Name;
false -> "JaneDoe"
end.
% pimp spawns new ho
p_spawn_ho(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) ->
% new name for ho, initial ho state
{N, NewNames} = recycle(Names),
InitHS = #hs{name = N, daddy = self()},
% spawn the new ho
ok = p_logfln("spawning new ho: ~ts", [N]),
SpawnHo = fun() -> h_init(LSock, InitHS) end,
{HPid, HRef} = erlang:spawn_monitor(SpawnHo),
% new ho record from Pimp POV
NewHo = #pho{pid = HPid, ref = HRef, name = N},
ok = p_logfln("spawned new ho: ~tw", [NewHo]),
NewPS = PS#ps{hos = [NewHo | Hos], ho_names = NewNames},
NewPS.
% ho down, remove her from roster
p_ho_down({'DOWN', _Ref, process, Pid, Info}, PS = #ps{hos = Hos}) ->
ok = p_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]),
NewHos = lists:keydelete(Pid, #pho.pid, Hos),
NewPS = PS#ps{hos = NewHos},
NewPS.
% echo message from one ho to all the rest
% skip if equal
p_echo_except(SrcPid, SrcName, Data, [#pho{pid = SrcPid} | Rest]) ->
p_echo_except(SrcPid, SrcName, Data, Rest);
p_echo_except(SrcPid, SrcName, Data, [#pho{pid = DstPid} | Rest]) ->
DstPid ! {pimp, send, Data},
p_echo_except(SrcPid, SrcName, Data, Rest);
p_echo_except(_, _, _, []) ->
ok.
%% runs in ho context
h_init(LSock, HS = #hs{name = Name, daddy = Daddy, asock = none}) ->
h_logln(Name, "h_init"),
% this is key: accept needs to run in ho context, otherwise she
% doesn't own the acceptor socket
%
% blocks until client connects
case gen_tcp:accept(LSock) of
{ok, ASock} ->
h_logln(Name, "taken"),
% tell daddy to spawn the next ho
Daddy ! {ho, self(), taken},
Welcome = io_lib:format("HI! I'm ~ts. What's your name?\r\n",
[Name]),
ok = h_send(Name, ASock, Welcome),
HS_II = HS#hs{asock = ASock},
h_loop(HS_II);
{error, closed} ->
h_logln(Name, "didn't get taken")
end.
h_loop(HS = #hs{name = Name, daddy = Daddy, asock = ASock}) ->
ok = inet:setopts(ASock, [{active, once}]),
receive
{tcp, ASock, Data} ->
Data_II = string:chomp(Data),
ok = h_rcvd(Name, Data_II),
Daddy ! {ho, self(), sent, Data_II},
h_loop(HS);
{pimp, send, Data} ->
ok = h_send(Name, ASock, Data),
h_loop(HS);
{tcp_closed, ASock} ->
exit(its_fine);
Unknown ->
ok = h_logfln(Name, "unknown message: ~tw", [Unknown]),
h_loop(HS)
end.
% pimp log in bold
p_logln(LogStr) ->
Msg = io_lib:format("!! Daddy :: ~ts~n", [LogStr]),
PimpChars = ?ANSI_BOLD([Msg]),
io:put_chars(PimpChars).
p_logfln(LogStr, Args) ->
p_logln(io_lib:format(LogStr, Args)).
% ho log dimmed
h_logln(HoName, LogStr) ->
FMsg = io_lib:format("!! ~ts :: ~ts~n", [HoName, LogStr]),
HoChars = ?ANSI_DIM([FMsg]),
io:put_chars(HoChars).
h_logfln(HoName, MsgStr, Args) ->
h_logln(HoName, io_lib:format(MsgStr, Args)).
% normal: john messages
h_rcvd(Name, MsgStr) ->
Msg = io_lib:format("<< ~ts :: ~ts~n", [Name, MsgStr]),
io:put_chars(Msg).
h_send(Name, Socket, Msg) ->
ok = io:format(">> ~ts :: ~ts~n", [Name, Msg]),
gen_tcp:send(Socket, Msg).
init_ho_names() ->
["Crystal", "Tiffany", "Amber", "Brandy", "Lola", "Angel",
"Ginger", "Candy", "Charity", "Anastasia", "Cherry", "Kitty",
"Jade", "Destiny", "Devon", "Chastity", "Raven", "Scarlett",
"Bambi", "Star", "Paris", "Dallas", "Diamond", "Skye",
"Trinity", "Tawny", "Layla", "Lexie", "Roxy", "Porsche",
"Nevaeh", "Ashlynn", "Aspen", "Chyna", "Lexus", "Unique",
"Chardonnay", "Houston", "London", "Coco", "Luscious",
"Delight", "Capri", "Trixie", "Cinnamon"].
recycle([N | Ns]) ->
{N, Ns ++ [N]}.