%% @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]). %% pimp state -record(ps, {lsock = none :: gen_tcp:socket(), hos = [] :: [{pid(), reference()}], ho_names = init_ho_names() :: [string()]}). -type pimp_state() :: #ps{}. %% ho state -record(hs, {name = none :: string(), daddy = none :: pid(), vagene = none :: gen_tcp:socket()}). -type ho_state() :: #hs{}. -spec main() -> no_return(). %% @doc %% run the echo server daemon main() -> %% show the splash screen nigga ok = splash(), case gen_tcp:listen(6969, [binary, {packet, line}, {active, once}]) of {ok, LSock} -> %% we out here %% we not wuz sockitz nigga %% we **be** sockets %% nigga ok = pimp_logfln("Butters iz in the hizzouse: ~tp", [self()]), pimp_loop(#ps{lsock = LSock}); {error, closed} -> %% dont be pimp io:format("PIMP ERROR: ~tp~n", [Error]) end. -spec splash() -> ok. %% @private %% show splash screen splash() -> SplashStr = """ RBX example 2 telnet echo server daemon "one" part of the many-to-one """, io:format("~s~n", [SplashStr]). -spec pimp_loop(PimpState) -> NoReturn when PimpState :: pimp_state(), NoReturn :: no_return(). %% @private %% runs in context of main pimp_loop(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) -> %% blocks until client connects case gen_tcp:accept(LSock) of {ok, ASock} -> {HoName, NewNames} = recycle_ho_names(Names), InitHoState = #hs{name = HoName, daddy = self(), vagene = ASock}, SpawnHo = fun() -> do_be_ho(InitHoState) end, NewHo = {_Pid, _Ref} = erlang:spawn_monitor(SpawnHo), NewPimpState = PS#ps{hos = [NewHo | Hos], ho_names = NewNames}, pimp_loop(NewPimpState); {echo, From, Data} -> echo_except(From, Data, Hos), pimp_loop(PS); {'DOWN', Ref, process, Pid, Info} -> ok = pimp_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]), NewHos = lists:delete({Pid, Ref}, Hos), NewPS = PS#ps{hos = NewHos}, pimp_loop(NewPS); Bad -> pimp_logfln("bad msg: ~tw", [Bad]), pimp_logln("liquidating the hos"), liquidate_hos(Hos), gen_tcp:close(LSock) end. liquidate_hos([{HoPid, _HoRef} | Hos]) -> pimp_logfln("liquidating ~tp", [HoPid]), exit(HoPid, kill), liquidate_hos(Hos); liquidate_hos([]) -> pimp_logln("it's done"). % skip if equal echo_except(SrcPid, Data, [{SrcPid, _Ref} | Rest]) -> echo_except(SrcPid, Data, Rest); echo_except(SrcPid, Data, [{Pid, _Ref} | Rest]) -> Pid ! {echo, Data}, echo_except(SrcPid, Data, Rest); echo_except(_, _, []) -> ok. -spec spawn_ho(HoState) -> -spec do_be_ho(HoState) -> NoReturn when HoState :: ho_state(), NoReturn :: no_return(). %% @doc %% ain't no rest for the wicked do_be_ho(HS = #hs{name = Name, daddy = Daddy, vagene = ASock}) -> Msg = io:format("HI! I'm ~ts. What's your name?\r\n", [Name]), ok = ho_send(ASock, Msg), receive {tcp, ASock, Data} -> ho_rcvd(Data), Daddy ! {echo, self(), Data}, do_be_ho(HS); {echo, Data} -> ho_send(ASock, Data), do_be_ho(HS); {tcp_closed, ASock} -> exit(its_fine) end. pimp_logfln(MsgStr, Args) -> pimp_logln(io_lib:format(MsgStr, Args)). pimp_logln(MsgStr) -> PimpPfx = io_lib:format("pimp ~tp :: ", [self()]), Msg = [?ANSI_BOLD([PimpPfx, MsgStr, "\n"])], ok = io:put_chars(Msg), ok. ho_rcvd(MsgStr) -> ok = io:format("ho ~tp << ~tw~n", [self(), MsgStr]), ok. ho_send(Socket, Msg) -> ok = io:format("ho ~tp >> ~tw~n", [self(), Msg]), ok = gen_tcp:send(Socket, Msg), ok. 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_ho_names([N | Ns]) -> {N, Ns ++ [N]}.