[英]Why does my simple Erlang server not close?
源文件:
-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 shell时,error_logger警告不匹配,但是客户端进程已经关闭。
为什么我的服务器关闭失败? 怎么了?
那里有一些古怪的东西。 讨论它们的最简单方法可能是显示一个不同的版本:
-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内部杀死自己,另一个使从系统内部处理意外消息。 “意外”子句很重要,其原因有两个:告诉我们正在发生的事情(您通常不应该接收神秘消息,但是它可能发生,并且并非总是您的错),并且它防止进程邮箱堆积带有无法匹配但无法管理的消息。
只是坚持使用上面的版本,这是我第一次会话时发生的事情...
在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.
如我们所见,来自客户端的关闭连接按预期终止。 外壳程序的进程正在监视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数据报一样工作,但这不是真的。 telnet客户端正确解释了服务器返回消息中的“ \\ r \\ n”并中断了下一行。
我解决了这个问题,但我不知道为什么...
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.
当您从shell调用q()
时,它将调用init:stop/0
。 根据文档的作用是:
在系统终止之前,所有应用程序均平稳运行,所有代码均已卸载,并且所有端口均已关闭。
调用start_server/0
,它将生成一个进程,并且该process
将打开一个用于TCP连接的port
。 因为您是从外壳程序内部启动服务器的,所以它链接到外壳程序,因此,当外壳程序收到退出信号时,它将再次将其发送到所有链接的进程,然后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()
,将卸载所有模块,并逐个关闭端口,因此将导致不必要的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.