简体   繁体   中英

Why does my simple Erlang server not close?

Source file:

-module(biu_server).
-export([start_server/0]).

start_server() -> 
  {ok, Listen} = gen_tcp:listen(1332,
         [binary, {packet, 4},{reuseaddr, true}, {active, true}]),
  spawn(fun() -> par_connect(Listen) end).

par_connect(Listen) ->
     {ok,Socket} = gen_tcp:accept(Listen),
     spawn(fun() -> par_connect(Listen) end),
     loop(Socket).

loop(Socket) ->
    receive
      {tcp, Socket, Request} ->
         gen_tcp:send(Socket, term_to_binary(Request)), 
         loop(Socket);
      {tcp_closed, Socket} ->
         io:format("Server socket closed~n")
    end.

Inside shell:

  1> c(biu_server).
  {ok,biu_server}
  2> biu_server:start_server().
  <0.40.0>
  3> q().
  ok
  4> {error_logger,{{2016,1,9},{18,40,28}},"Error in process ~p with exit value:~n~p~n",[<0.40.0>,{{badmatch,{error,closed}},[{biu_server,par_connect,1,[{file,"biu_server.erl"},{line,11}]}]}]}

I want to write a echo server, but when I quit erlang shell, the error_logger is warning badmatch, but the client process is already closed.

Why does my server close fail? What happens?

There are a few wonky things in there. The easiest way to talk through them is probably to just show a different version:

-module(biu_server).
-export([start_server/0,start/0]).

start() ->
    spawn(fun() -> start_server() end).

start_server() ->
    Options = [list, {reuseaddr, true}],
    {ok, Listen} = gen_tcp:listen(1337, Options),
    par_connect(Listen).

par_connect(Listen) ->
    {ok, Socket} = gen_tcp:accept(Listen),
%     The line below is a whole different universe of confusion for now.
%     Get sequential stuff figured out first.
%   spawn(fun() -> par_connect(Listen) end),
    loop(Socket).

loop(Socket) ->
    ok = inet:setopts(Socket, [{active, once}]),
    receive
        {tcp, Socket, Request} ->
            ok = io:format("Received TCP message: ~tp~n", [Request]),
            Response = io_lib:format("You said: ~tp\r\n", [Request]),
            gen_tcp:send(Socket, Response),
            loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Server socket closed~n");
        quit ->
            ok = io:format("Fine! I QUIT!~n");
        Unexpected ->
            ok = io:format("Unexpected message: ~tp~n", [Unexpected]),
            loop(Socket)
    end.

Note that above I commented out the call to spawn a new process to handle the connection. One of the problems the original version had was that the remaining listener had no way to be terminated because it was always blocking on TCP accept -- forever.

There are also some socket control issues there -- the easiest way around that is to have each listener spawn the next listener and move on to handle its newly acquired connection. Another way is to do what you've done, but there are some details that need to be managed to make it work smoothly. (Example (don't worry, still easy): https://github.com/zxq9/erlmud/blob/master/erlmud-0.1/tcplistener.erl ).

I also added two new clauses to the receive in your main loop: one that lets us tell the process to kill itself from within Erlang, and another that handles unexpected messages from within the system. The "Unexpected" clause is important for two reasons: it tells us what is going on (you shouldn't normally be receiving mysterious messages, but it can happen and its not always your fault), and it prevents the process mailbox from stacking up with unmatched-but-unmanageable messages.

Just sticking with the version above, here is what happens during my first session...

In the Erlang shell:

1> c(biu_server).
{ok,biu_server}
2> {Pid, Ref} = spawn_monitor(biu_server, start_server, []).
{<0.40.0>,#Ref<0.0.2.89>}
Received TCP message: "foo\r\n"
Received TCP message: "bar\r\n"
Received TCP message: "Yay! It works!\r\n"
Server socket closed
3> flush().
Shell got {'DOWN',#Ref<0.0.2.89>,process,<0.40.0>,normal}
ok
4> f().                                                     
ok

And in a telnet session:

ceverett@changa:~/Code/erlang$ telnet localhost 1337
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
foo
You said: "foo\r\n"
bar
You said: "bar\r\n"
Yay! It works!
You said: "Yay! It works!\r\n"
^]
telnet> quit
Connection closed.

As we can see, the closed connection from the client side terminated as expected. The shell's process was monitoring the TCP server process, so when I flush() at the end we see the 'DOWN' monitor message as expected -- with a normal exit.

Now let's do a similar session, but we'll use the Erlang-side quit message:

5> {Pid, Ref} = spawn_monitor(biu_server, start_server, []).
{<0.43.0>,#Ref<0.0.2.103>}
Received TCP message: "Still works.\r\n"
6> Pid ! "Launch the missiles!".
Unexpected message: "Launch the missiles!"
"Launch the missiles!"
7> Pid ! quit.
Fine! I QUIT!
quit
8> flush().
Shell got {'DOWN',#Ref<0.0.2.103>,process,<0.43.0>,normal}
ok

And on the telnet side:

ceverett@changa:~/Code/erlang$ telnet localhost 1337
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Still works. 
You said: "Still works.\r\n"
Connection closed by foreign host.

You notice the "\\r\\n" in there. That is coming from the client's original message -- all telnet messages are terminated with "\\r\\n" (so this is what you split the stream on usually -- which is a topic we aren't addressing yet; this code actually is pretending that TCP works like UDP datagrams, but that's not true...). The "\\r\\n" that are in the server's return message are being interpreted correctly by the telnet client and breaking to the next line.

Writing telnet (or datagram) games is a very pleasant way to explore the Erlang networking modules, by the way.

i solve this question,but i don't know why...

http://erlang.2086793.n4.nabble.com/parallel-tcp-server-closed-once-spawned-td2099538.html

code:

-module(biu_server).
-export([start_server/0,start/0]).

start() ->
    spawn(fun() -> start_server() end).

start_server() -> 
    {ok, Listen} = gen_tcp:listen(1332,
         [binary, {packet, 4},{reuseaddr, true}, {active, true}]),
    par_connect(Listen).

par_connect(Listen) ->
    {ok,Socket} = gen_tcp:accept(Listen),
    spawn(fun() -> par_connect(Listen) end),
    loop(Socket).

loop(Socket) ->
    receive
    {tcp, Socket, Request} ->
       gen_tcp:send(Socket, term_to_binary(Request)), 
       loop(Socket);
    {tcp_closed, Socket} ->
       io:format("Server socket closed~n")
    end.

When you call q() from shell, it calls init:stop/0 . What it does based on documentation is:

All applications are taken down smoothly, all code is unloaded, and all ports are closed before the system terminates.

When you call start_server/0 , it spawns a process and that process opens a port for TCP connection. Because you start your server from inside the shell, so it is linked to the shell, so when the shell gets an exit signal, it sends it again to all the linked processes, then the following error report will be produced by the error_logger module.

1> biu:start_server().
<0.35.0>
2> exit(normal).

=ERROR REPORT==== 9-Jan-2016::16:40:29 ===
Error in process <0.35.0> with exit value: {{badmatch,{error,closed}},[{biu,par_connect,1,[{file,"biu.erl"},{line,12}]}]}

** exception exit: normal
3> 

With knowing this, when you call q() before closing the port, all the modules are unloaded and ports are closed one by one, so the unwanted behaviour will result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM