客户端在发送消息后关闭,为什么gen_tcp与opts {active,false}接受两次

时间:2015-12-03 12:22:10

标签: tcp network-programming erlang gen-tcp

我只是用gen_tcp进行测试。一个简单的echo服务器和一个客户端。

但是客户端启动和关闭,服务器接受两个连接,一个是好的,另一个是坏的。

我的演示脚本的任何问题,以及如何解释它?

服务器

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>

客户端

CATQ()

```

1 个答案:

答案 0 :(得分:3)

  

但是客户端启动和关闭,服务器接受两个连接,一个是好的,另一个是坏的。

实际上,您的服务器只接受了一个连接:

  1. 接受来自客户端的连接后输入loop/1
  2. inet:peername/1返回{ok,{{192,168,2,184},51608}},因为套接字仍处于打开状态
  3. gen_tcp:recv/2返回客户端发送的<<"aa">>
  4. gen_tcp:send/2将数据从3发送到客户端
  5. 再次输入loop/1
  6. inet:peername/1返回{error,enotconn},因为套接字已被客户端
  7. 关闭
  8. gen_tcp:recv/2返回{error,closed}
  9. 流程正常退出
  10. 所以实际上,你的echo服务器运行正常,但是@ 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

    在客户端等待echo服务器在关闭套接字之前发回数据。

    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样式代码,可能不应该用于教育以外的任何事情。

    更好的方法可能是使用ranchgen_listener_tcp之类的内容来监听和接受连接。这两个项目都有回显服务器的示例:tcp_echo (ranch)echo_server.erl (gen_listener_tcp)