%% @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: %% %% %% %% ``` %% 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]}.