为什么我的简单Erlang服务器不关闭?

时间:2016-01-09 11:06:47

标签: erlang erlang-escript

源文件:

-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.

内壳:

  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}]}]}]}

我想编写一个echo服务器,但是当我退出erlang shell时,error_logger会警告badmatch,但客户端进程已经关闭。

为什么我的服务器关闭失败?会发生什么?

3 个答案:

答案 0 :(得分:1)

那里有一些不稳定的东西。通过它们进行交谈的最简单方法可能是展示不同的版本:

-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.

请注意,上面我注释了生成处理连接的新进程的调用。原始版本存在的问题之一是剩余侦听器无法终止,因为它总是始终阻止 TCP接受 - 永远。

还有一些套接字控制问题 - 最简单的方法是让每个侦听器生成下一个侦听器并继续处理其新获取的连接。另一种方法是做你已经完成的事情,但是需要管理一些细节以使其顺利运行。 (示例(不要担心,仍然很容易):https://github.com/zxq9/erlmud/blob/master/erlmud-0.1/tcplistener.erl)。

我还在主循环中为receive添加了两个新子句:一个允许我们告诉进程从Erlang内部自杀,另一个用于处理来自系统内部的意外消息。 “意外”条款很重要有两个原因:它告诉我们发生了什么(你不应该通常接收神秘的消息,但它可以发生而不是总是你的错误),它会阻止进程邮箱堆叠不匹配但无法管理的邮件。

坚持上面的版本,这是我第一次会议期间发生的事情......

在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

在telnet会话中:

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.

正如我们所看到的,来自客户端的关闭连接按预期终止。 shell的进程正在监视TCP服务器进程,所以当我flush()结束时,我们会按预期看到'DOWN'监视器消息 - 正常退出。

现在让我们进行类似的会话,但我们将使用Erlang端quit消息:

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

在telnet方面:

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.

你注意到那里的“\ r \ n”。这是来自客户端的原始消息 - 所有telnet消息都以“\ r \ n”结束(所以这就是你通常拆分流的内容 - 这是我们不是的主题尚未解决;此代码实际假装TCP的工作方式与UDP数据报类似,但事实并非如此......)。服务器返回消息中的“\ r \ n”正由telnet客户端正确解释并突破到下一行。

顺便说一句,编写telnet(或数据报)游戏是a very pleasant way to explore Erlang网络模块。

答案 1 :(得分:0)

我解决了这个问题,但我不知道为什么......

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

代码:

-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.

答案 2 :(得分:0)

当你从shell调用q()时,它会调用init:stop/0。它基于文档做的是:

  

所有应用程序都会顺利关闭,所有代码都会被卸载,所有端口都会在系统终止之前关闭。

当您致电start_server/0时,它会生成一个流程,并且process会为TCP连接打开port。因为你从shell内部启动你的服务器,所以它链接到shell,所以当shell获得一个退出信号时,它再次将它发送到所有链接的进程,然后{{1将生成以下错误报告模块。

error_logger

知道了这一点,当您在关闭端口之前调用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> 时,所有模块都会被卸载并且端口会逐个关闭,因此会产生不需要的行为。