wip echo server
This commit is contained in:
@@ -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]}.
|
||||
Reference in New Issue
Block a user