[erlang-questions] {active, N} to build an echo TCP server?

Frank Muller frank.muller.erl@REDACTED
Wed Dec 28 06:14:10 CET 2016


Hi Craig

Exactly what I wanted to know. Thank you for the clear explanations.

One last question: how one can determine the best value of N? (i.e not too
big to avoid overwhelming the server, not too small to avoid N close to 1.

/Frank

Le mer. 28 déc. 2016 à 05:20, zxq9 <zxq9@REDACTED> a écrit :

> On 2016年12月28日 水曜日 01:19:26 Frank Muller wrote:
>
> > Hi guys
>
> >
>
> > While able to build an echo TCP server which uses {active, once} and
> works
>
> > as expected,
>
> > I’m unable to design the same echo server using {active, N}.
>
> >
>
> > Can someone help me understand how to use it?
>
> > How and where to decrement the N?
>
> > What happens when N reaches 0?
>
>
>
> Hi, Frank.
>
>
>
> I don't know any better way to explain this than with an example.
>
> Below is an echo server that can talk to a telnet client.
>
> There are two versions in this module.
>
>
>
> The first is an {active, once} version that closes when it receives
>
> "bye\r\n" from the client or the connection is closed. No surprises
>
> there.
>
>
>
> The second is an {active, Count} version that gets stuck when it
>
> runs through its count and notifies the client of the situation.
>
> This version can accept an Erlang message to its process to reset
>
> the count.
>
>
>
>
>
>
>
> -module(echoserve).
>
> -export([start/1, start/2]).
>
>
>
>
>
> %%% The {active, once} version
>
>
>
> start(PortNum) ->
>
>     spawn_link(fun() -> listen(PortNum) end).
>
>
>
>
>
> listen(PortNum) ->
>
>     {ok, Listener} = gen_tcp:listen(PortNum, [{active, false}, {mode,
> list}]),
>
>     accept(Listener).
>
>
>
>
>
> accept(Listener) ->
>
>     {ok, Socket} = gen_tcp:accept(Listener),
>
>     loop(Socket).
>
>
>
>
>
> loop(Socket) ->
>
>     ok = inet:setopts(Socket, [{active, once}]),
>
>     receive
>
>         {tcp, Socket, "bye\r\n"} ->
>
>             ok = io:format("~p: Client is retiring.~n", [self()]),
>
>             ok = gen_tcp:send(Socket, "Bye!\r\n"),
>
>             ok = gen_tcp:shutdown(Socket, read_write),
>
>             exit(normal);
>
>         {tcp, Socket, Data} ->
>
>             ok = io:format("~p: Received ~tp~n", [self(), Data]),
>
>             Message = ["You sent ", Data],
>
>             ok = gen_tcp:send(Socket, Message),
>
>             loop(Socket);
>
>         {tcp_closed, Socket} ->
>
>             ok = io:format("~p: Connection closed, retiring.~n", [self()]),
>
>             exit(normal);
>
>         Unexpected ->
>
>             ok = io:format("~p: Unexpected message ~tp. Retiring.~n",
> [self(), Unexpected]),
>
>             exit(normal)
>
>     end.
>
>
>
>
>
>
>
> %%% The {active, Count} version
>
>
>
> start(PortNum, ActiveCount) ->
>
>     spawn_link(fun() -> listen(PortNum, ActiveCount) end).
>
>
>
>
>
> listen(PortNum, Count) ->
>
>     {ok, Listener} = gen_tcp:listen(PortNum, [{active, false}, {mode,
> list}]),
>
>     accept(Listener, Count).
>
>
>
>
>
> accept(Listener, Count) ->
>
>     {ok, Socket} = gen_tcp:accept(Listener),
>
>     ok = inet:setopts(Socket, [{active, Count}]),
>
>     StringCount = integer_to_list(Count),
>
>     Greeting = ["Hi! I am a telnet echo server\r\n"
>
>                 "I will only respond to you ", StringCount, " times unless
> "
>
>                 "the operator sends me a {set, Count} message.\r\n"],
>
>     ok = gen_tcp:send(Socket, Greeting),
>
>     countdown_loop(Socket).
>
>
>
>
>
> countdown_loop(Socket) ->
>
>     receive
>
>         {tcp, Socket, "bye\r\n"} ->
>
>             ok = io:format("~p: Client is retiring.~n", [self()]),
>
>             ok = gen_tcp:send(Socket, "Bye!\r\n"),
>
>             ok = gen_tcp:shutdown(Socket, read_write),
>
>             exit(normal);
>
>         {tcp, Socket, Data} ->
>
>             ok = io:format("~p: Received ~tp~n", [self(), Data]),
>
>             Message = ["You sent ", Data],
>
>             ok = gen_tcp:send(Socket, Message),
>
>             countdown_loop(Socket);
>
>         {tcp_passive, Socket} ->
>
>             Message = "Oh noes! I've just run out of responses. Whatever
> you send will be "
>
>                       "buffered until the operator sends me a {set, Count}
> message!\r\n"
>
>                       "This also means that I won't respond to a \"bye\"
> message from you "
>
>                       "until I am listening again, and only if the buffer
> is clear before you "
>
>                       "send that message.\r\n",
>
>             ok = gen_tcp:send(Socket, Message),
>
>             countdown_loop(Socket);
>
>         {tcp_closed, Socket} ->
>
>             ok = io:format("~p: Connection closed, retiring.~n", [self()]),
>
>             exit(normal);
>
>         {set, Count} ->
>
>             StringCount = integer_to_list(Count),
>
>             Message = ["The operator set me to respond to ", StringCount,
> " messages from now!\r\n"],
>
>             ok = gen_tcp:send(Socket, Message),
>
>             ok = inet:setopts(Socket, [{active, Count}]),
>
>             countdown_loop(Socket);
>
>         Unexpected ->
>
>             ok = io:format("~p: Unexpected message ~tp. Retiring.~n",
> [self(), Unexpected]),
>
>             exit(normal)
>
>     end.
>
>
>
>
>
>
>
>
>
> Hopefully your email client didn't choke on my too-long lines -- I sort
>
> of scribbled this out in a hurry.
>
>
>
> Anyway, let's run the first one and see what happens...
>
>
>
> Server side:
>
>
>
>   1> c(echoserve).
>
>   {ok,echoserve}
>
>   2> echoserve:start(7777).
>
>   <0.64.0>
>
>   <0.64.0>: Received "foo\r\n"
>
>   <0.64.0>: Received "bar\r\n"
>
>   <0.64.0>: Received "baz\r\n"
>
>   <0.64.0>: Client is retiring.
>
>
>
>
>
> Client side:
>
>
>
>   ceverett@REDACTED:~/Code/erlang$ telnet localhost 7777
>
>   Trying 127.0.0.1...
>
>   Connected to localhost.
>
>   Escape character is '^]'.
>
>   foo
>
>   You sent foo
>
>   bar
>
>   You sent bar
>
>   baz
>
>   You sent baz
>
>   bye
>
>   Bye!
>
>   Connection closed by foreign host.
>
>
>
>
>
> Pretty much what you would expect.
>
>
>
> Now let's try the second version...
>
>
>
> Server side:
>
>
>
>   3> echoserve:start(7777, 3).
>
>   <0.66.0>
>
>   <0.66.0>: Received "foo\r\n"
>
>   <0.66.0>: Received "bar\r\n"
>
>   <0.66.0>: Received "baz\r\n"
>
>   4> pid(0,66,0) ! {set, 3}.
>
>   <0.66.0>: Received "bye\r\nwhat?\r\nWhy won't you close?!?\r\n"
>
>   {set,3}
>
>   <0.66.0>: Client is retiring.
>
>
>
>
>
> Client side:
>
>
>
>   ceverett@REDACTED:~/Code/erlang$ telnet localhost 7777
>
>   Trying 127.0.0.1...
>
>   Connected to localhost.
>
>   Escape character is '^]'.
>
>   Hi! I am a telnet echo server
>
>   I will only respond to you 3 times unless the operator sends me a {set,
> Count} message.
>
>   foo
>
>   You sent foo
>
>   bar
>
>   You sent bar
>
>   baz
>
>   You sent baz
>
>   Oh noes! I've just run out of responses. Whatever you send will be
> buffered until the operator sends me a {set, Count} message!
>
>   This also means that I won't respond to a "bye" message from you until I
> am listening again, and only if the buffer is clear before you send that
> message.
>
>   bye
>
>   what?
>
>   Why won't you close?!?
>
>   The operator set me to respond to 3 messages from now!
>
>   You sent bye
>
>   what?
>
>   Why won't you close?!?
>
>   bye
>
>   Bye!
>
>   Connection closed by foreign host.
>
>
>
>
>
> So this was a little bit different. Take your time reading over what
> happened
>
> in the second exchange between the client and server. I don't really have a
>
> good way to display this histographically in text here (at least not
> without
>
> spending a lot of time doing it). I might make a blog post out of this
> later.
>
>
>
> Note that the "bye\r\n" sent by the client did not match the "bye\r\n"
> clause
>
> in the receive of countdown_loop/1. Why not? Because TCP is a stream, not a
>
> datagram. What was received by the server was:
>
> "bye\r\nwhat?\r\nWhy won't you close?!?\r\n"
>
>
>
> It was squished together because that's just how much stuff was in the
> buffer
>
> by the time the socket was read from again. The moral of the story? Don't
> ever
>
> fool yourself into thinking you're dealing with datagrams.
>
>
>
> Also note that the code above represents only a single process as the
> listener
>
> and socket handler. The concurrent version of this is pretty similar,
> though.
>
>
>
> I hope I explained more than confused.
>
>
>
> -Craig
>
> _______________________________________________
>
> erlang-questions mailing list
>
> erlang-questions@REDACTED
>
> http://erlang.org/mailman/listinfo/erlang-questions
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20161228/748af364/attachment.htm>


More information about the erlang-questions mailing list