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

zxq9 zxq9@REDACTED
Wed Dec 28 05:19:55 CET 2016


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



More information about the erlang-questions mailing list