1

我只是用 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>

```

4

1 回答 1

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. gen_tcp:recv/2返回{error,closed}
  8. 进程正常退出

因此,实际上,您的回声服务器运行良好,但是@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 风格的代码,可能不应该用于教育目的以外的任何事情。

更好的方法可能是使用类似牧场gen_listener_tcp的服务器端侦听和接受连接。这两个项目都有 echo 服务器的示例:tcp_echo (ranch)echo_server.erl (gen_listener_tcp)

于 2015-12-03T21:58:33.253 回答