wip echo server

This commit is contained in:
Peter Harpending
2026-09-01 21:24:15 -07:00
parent 8f8e10bf28
commit 997c3d1da3
9 changed files with 816 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
%% @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]).
%% 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]}.