发送消息后客户端关闭,为什么 gen_tcp with opts {active, false} accept 两次
Client closed after sending a message, why gen_tcp with opts {active, false} accept twice
我只是用 gen_tcp 做了一个测试。一台简单的回显服务器和一台客户端。
但是客户端启动和关闭,服务器接受两个连接,一个是好的,另一个是坏的。
我的演示脚本有任何问题,如何解释?
服务器
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> loop(Socket) end),
accept(LSocket).
loop(Socket) ->
timer:sleep(10000),
P = inet:peername(Socket),
io:format("ok ~p~n", [P]),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
ok;
E ->
io:format("bad ~p~n", [E])
end.
演示服务器
1> c(echo).
{ok,echo}
2> echo:listen(1111).
ok {ok,{{192,168,2,184},51608}}
ok {error,enotconn}
客户
> spawn(fun() -> {ok, P} = gen_tcp:connect("192.168.2.173", 1111, []), gen_tcp:send(P, "aa"), gen_tcp:close(P) end).
<0.64.0>
```
But client started and closed, server accept two connection, and one is ok, the other is bad.
实际上,您的服务器只接受了一个连接:
- 接受客户端连接后输入
loop/1
inet:peername/1
returns {ok,{{192,168,2,184},51608}}
因为套接字仍然打开
gen_tcp:recv/2
returns <<"aa">>
客户端发送
gen_tcp:send/2
将3的数据发送给客户端
- 再次输入
loop/1
inet:peername/1
returns {error,enotconn}
因为套接字被客户端关闭了
gen_tcp:recv/2
returns {error,closed}
- 进程正常退出
所以实际上,您的回显服务器运行良好,但是@zxq9 的评论中提到了一些可以改进的地方。
改进 1
将接受的套接字的控制权移交给新生成的进程。
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, CSocket} = gen_tcp:accept(LSocket),
Ref = make_ref(),
To = spawn(fun() -> init(Ref, CSocket) end),
gen_tcp:controlling_process(CSocket, To),
To ! {handoff, Ref, CSocket},
accept(LSocket).
init(Ref, Socket) ->
receive
{handoff, Ref, Socket} ->
{ok, Peername} = inet:peername(Socket),
io:format("[S] peername ~p~n", [Peername]),
loop(Socket)
end.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[S] got ~p~n", [Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
io:format("[S] closed~n", []);
E ->
io:format("[S] error ~p~n", [E])
end.
改进 2
在关闭套接字之前,在客户端等待回显服务器发回数据。
spawn(fun () ->
{ok, Socket} = gen_tcp:connect("127.0.0.1", 1111, [binary, {packet, 0}, {active, false}]),
{ok, Peername} = inet:peername(Socket),
io:format("[C] peername ~p~n", [Peername]),
gen_tcp:send(Socket, <<"aa">>),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[C] got ~p~n", [Data]),
gen_tcp:close(Socket);
{error, closed} ->
io:format("[C] closed~n", []);
E ->
io:format("[C] error ~p~n", [E])
end
end).
例子
服务器应如下所示:
1> c(echo).
{ok,echo}
2> echo:listen(1111).
[S] peername {{127,0,0,1},57586}
[S] got <<"aa">>
[S] closed
客户端应如下所示:
1> % paste in the code from Improvement 2
<0.34.0>
[C] peername {{127,0,0,1},1111}
[C] got <<"aa">>
建议
正如@zxq9 提到的,这是不是 OTP 风格的代码,可能不应该用于教育以外的任何目的。
更好的方法可能是使用 ranch or gen_listener_tcp for the server side listening and accepting of connections. Both projects have examples of echo servers: tcp_echo (ranch) and echo_server.erl (gen_listener_tcp).
我只是用 gen_tcp 做了一个测试。一台简单的回显服务器和一台客户端。
但是客户端启动和关闭,服务器接受两个连接,一个是好的,另一个是坏的。
我的演示脚本有任何问题,如何解释?
服务器
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> loop(Socket) end),
accept(LSocket).
loop(Socket) ->
timer:sleep(10000),
P = inet:peername(Socket),
io:format("ok ~p~n", [P]),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
ok;
E ->
io:format("bad ~p~n", [E])
end.
演示服务器
1> c(echo).
{ok,echo}
2> echo:listen(1111).
ok {ok,{{192,168,2,184},51608}}
ok {error,enotconn}
客户
> spawn(fun() -> {ok, P} = gen_tcp:connect("192.168.2.173", 1111, []), gen_tcp:send(P, "aa"), gen_tcp:close(P) end).
<0.64.0>
```
But client started and closed, server accept two connection, and one is ok, the other is bad.
实际上,您的服务器只接受了一个连接:
- 接受客户端连接后输入
loop/1
inet:peername/1
returns{ok,{{192,168,2,184},51608}}
因为套接字仍然打开gen_tcp:recv/2
returns<<"aa">>
客户端发送gen_tcp:send/2
将3的数据发送给客户端- 再次输入
loop/1
inet:peername/1
returns{error,enotconn}
因为套接字被客户端关闭了gen_tcp:recv/2
returns{error,closed}
- 进程正常退出
所以实际上,您的回显服务器运行良好,但是@zxq9 的评论中提到了一些可以改进的地方。
改进 1
将接受的套接字的控制权移交给新生成的进程。
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, CSocket} = gen_tcp:accept(LSocket),
Ref = make_ref(),
To = spawn(fun() -> init(Ref, CSocket) end),
gen_tcp:controlling_process(CSocket, To),
To ! {handoff, Ref, CSocket},
accept(LSocket).
init(Ref, Socket) ->
receive
{handoff, Ref, Socket} ->
{ok, Peername} = inet:peername(Socket),
io:format("[S] peername ~p~n", [Peername]),
loop(Socket)
end.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[S] got ~p~n", [Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
io:format("[S] closed~n", []);
E ->
io:format("[S] error ~p~n", [E])
end.
改进 2
在关闭套接字之前,在客户端等待回显服务器发回数据。
spawn(fun () ->
{ok, Socket} = gen_tcp:connect("127.0.0.1", 1111, [binary, {packet, 0}, {active, false}]),
{ok, Peername} = inet:peername(Socket),
io:format("[C] peername ~p~n", [Peername]),
gen_tcp:send(Socket, <<"aa">>),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[C] got ~p~n", [Data]),
gen_tcp:close(Socket);
{error, closed} ->
io:format("[C] closed~n", []);
E ->
io:format("[C] error ~p~n", [E])
end
end).
例子
服务器应如下所示:
1> c(echo).
{ok,echo}
2> echo:listen(1111).
[S] peername {{127,0,0,1},57586}
[S] got <<"aa">>
[S] closed
客户端应如下所示:
1> % paste in the code from Improvement 2
<0.34.0>
[C] peername {{127,0,0,1},1111}
[C] got <<"aa">>
建议
正如@zxq9 提到的,这是不是 OTP 风格的代码,可能不应该用于教育以外的任何目的。
更好的方法可能是使用 ranch or gen_listener_tcp for the server side listening and accepting of connections. Both projects have examples of echo servers: tcp_echo (ranch) and echo_server.erl (gen_listener_tcp).