Почему мой простой сервер Erlang не закрывается?

Исходный файл:

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

Я хочу написать эхо-сервер, но когда я закрываю оболочку erlang, error_logger предупреждает о несоответствии, но клиентский процесс уже закрыт.

Почему мой сервер не работает? Что просходит?

3 ответа

Там есть несколько странных вещей. Самый простой способ обсудить их, возможно, просто показать другую версию:

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

Я также добавил два новых предложения к получению в вашем основном цикле: одно позволяет нам сказать процессу убить себя изнутри Erlang, а другое - обрабатывать неожиданные сообщения изнутри системы. Предложение "Неожиданно" важно по двум причинам: оно сообщает нам, что происходит (обычно вы не должны получать таинственные сообщения, но это может произойти, и это не всегда ваша ошибка), и предотвращает суммирование почтового ящика процесса. с непревзойденными, но неуправляемыми сообщениями.

Просто придерживаясь версии выше, вот что происходит во время моей первой сессии...

В оболочке Эрланга:

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

И в сеансе телнет:

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.

Как мы видим, закрытое соединение со стороны клиента завершено, как и ожидалось. Процесс оболочки контролировал процесс 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 (или дейтаграмм), кстати, очень приятный способ изучить сетевые модули Erlang.

Когда вы звоните q() из оболочки, это вызывает init:stop/0, Что он делает на основании документации:

Все приложения удаляются без проблем, весь код выгружается, и все порты закрываются до завершения работы системы.

Когда вы звоните start_server/0это порождает процесс и process открывает port для TCP соединения. Поскольку вы запускаете сервер изнутри оболочки, поэтому он связан с оболочкой, поэтому, когда оболочка получает сигнал выхода, она отправляет его снова всем связанным процессам, тогда следующий отчет об ошибке будет создан 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> 

Зная это, когда вы звоните q() перед закрытием порта все модули выгружаются, а порты закрываются один за другим, что приведет к нежелательному поведению.

Я решаю этот вопрос, но я не знаю почему...

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.
Другие вопросы по тегам