diff --git a/README.md b/README.md index 1b98763..94f13a5 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,15 @@ - client connects to server - server writes back "hello" - server closes connection + +- x01-noisy-hello: + - was going to be hello world over noise + - abandoned + +- `x02-_`: + - skipped because the `2` key on my laptop doesn't work + +- x03-echo: + - telnet echo server + - budget version of zx template "traditional erlang service + application" diff --git a/x02-echo/echoc.erl b/x02-echo/echoc.erl deleted file mode 100644 index f2fc793..0000000 --- a/x02-echo/echoc.erl +++ /dev/null @@ -1,7 +0,0 @@ -% @doc echo server client --module(echoc). - --export([main/0]). - -main() -> - io:format("you can't help people who won't help themselves.~n", []). diff --git a/x02-echo/echod.erl b/x02-echo/echod.erl deleted file mode 100644 index daa8ce1..0000000 --- a/x02-echo/echod.erl +++ /dev/null @@ -1,265 +0,0 @@ -%% @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]}. diff --git a/x02-echo/ansi.hrl b/x03-echo/ansi.hrl similarity index 100% rename from x02-echo/ansi.hrl rename to x03-echo/ansi.hrl diff --git a/x03-echo/echoc.erl b/x03-echo/echoc.erl new file mode 100644 index 0000000..9fe70f8 --- /dev/null +++ b/x03-echo/echoc.erl @@ -0,0 +1,20 @@ +% @doc echo server client +-module(echoc). + +-export([main/0]). + +main() -> + {ok, Socket} = gen_tcp:connect("localhost", 6969, [binary, {packet, 0}]), + loop(Socket). + + +loop(Socket) -> + ok = inet:setopts(Socket, [{active, once}]), + receive + {tcp, Socket, Bytes} -> + ok = io:format("< ~tp~n", [Bytes]), + loop(Socket); + {tcp_closed, Socket} -> + ok = io:format("connection terminated~n", []), + exit(normal) + end. diff --git a/x03-echo/echod.erl b/x03-echo/echod.erl new file mode 100644 index 0000000..c438788 --- /dev/null +++ b/x03-echo/echod.erl @@ -0,0 +1,249 @@ +%% @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]}. diff --git a/x02-echo/johns.txt b/x03-echo/johns.txt similarity index 100% rename from x02-echo/johns.txt rename to x03-echo/johns.txt diff --git a/x03-echo/johns_ii.txt b/x03-echo/johns_ii.txt new file mode 100644 index 0000000..d178b94 --- /dev/null +++ b/x03-echo/johns_ii.txt @@ -0,0 +1,62 @@ + +Alexander +Anthony +Asher +Beau +Benjamin +Bennett +Caleb +Carter +Charles +Cooper +Daniel +David +Dylan +Elias +Elijah +Ethan +Ezekiel +Ezra +Gabriel +Grayson +Henry +Hudson +Isaac +Isaiah +Jack +Jackson +Jacob +James +John +Joseph +Josiah +Julian +Leo +Levi +Liam +Logan +Luca +Lucas +Luke +Mason +Mateo +Matthew +Maverick +Michael +Miles +Noah +Oliver +Owen +Roman +Rowan +Samuel +Santiago +Sebastian +Theodore +Thiago +Thomas +Waylon +Wesley +Weston +William +Wyatt diff --git a/x02-echo/names.txt b/x03-echo/names.txt similarity index 100% rename from x02-echo/names.txt rename to x03-echo/names.txt diff --git a/x02-echo/names2.txt b/x03-echo/names2.txt similarity index 100% rename from x02-echo/names2.txt rename to x03-echo/names2.txt diff --git a/x02-echo/names3.txt b/x03-echo/names3.txt similarity index 100% rename from x02-echo/names3.txt rename to x03-echo/names3.txt diff --git a/x03-echo/scratch.erl b/x03-echo/scratch.erl new file mode 100644 index 0000000..c629f18 --- /dev/null +++ b/x03-echo/scratch.erl @@ -0,0 +1,9 @@ + +liquidate_hos([{HoPid, _HoRef} | Hos]) -> + p_logfln("liquidating ~tp", [HoPid]), + exit(HoPid, kill), + liquidate_hos(Hos); +liquidate_hos([]) -> + p_logln("it's done"). + +