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