[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