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

客户端在发送消息后关闭,为什么gen_tcp和opts{active,false}接受两次,tcp,network-programming,erlang,gen-tcp,Tcp,Network Programming,Erlang,Gen Tcp,我只是用gen_tcp做了一个测试。一个简单的echo服务器和一个客户端 但客户端启动并关闭,服务器接受两个连接,一个正常,另一个坏 我的演示脚本有什么问题吗?如何解释 服务器 -模块(echo)。 -导出([listen/1])。 -定义(TCP_选项,[二进制,{packet,0},{active,false},{reuseaddr,true}])。 监听(端口)-> {ok,LSocket}=gen_tcp:listen(端口,tcp_选项), 接受(LSocket)。 接受(LSocke

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

但客户端启动并关闭,服务器接受两个连接,一个正常,另一个坏

我的演示脚本有什么问题吗?如何解释

服务器
-模块(echo)。
-导出([listen/1])。
-定义(TCP_选项,[二进制,{packet,0},{active,false},{reuseaddr,true}])。
监听(端口)->
{ok,LSocket}=gen_tcp:listen(端口,tcp_选项),
接受(LSocket)。
接受(LSocket)->
{ok,Socket}=gen_tcp:accept(LSocket),
繁殖(fun()->循环(套接字)结束),
接受(LSocket)。
循环(套接字)->
计时器:睡眠(10000),
P=inet:peername(插座),
io:格式(“ok~p~n,[p]),
案例gen_tcp:recv(套接字,0)的
{好的,数据}->
gen_tcp:发送(套接字、数据),
回路(插座);
{错误,已关闭}->
好啊
E->
io:格式(“错误的~p~n”,[E])
结束。
演示服务器

1>c(echo)。
{好的,回声}
2> 回声:听(1111)。
好{好{192168,2184},51608}
ok{错误,enotconn}
客户
>spawn(fun()->{ok,P}=gen_-tcp:connect(“192.168.2.173”,1111,[]),gen_-tcp:send(P,“aa”),gen_-tcp:close(P)end)。
```

但客户端启动并关闭,服务器接受两个连接,一个正常,另一个坏

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

  • 接受客户端连接后,输入
    loop/1
  • inet:peername/1
    返回
    {ok,{{192168,2184},51608}}
    ,因为套接字仍然打开
  • gen\u tcp:recv/2
    返回客户端发送的
  • gen_tcp:send/2
    将数据从3发送到客户端
  • 再次输入
    loop/1
  • inet:peername/1
    返回
    {error,enotconn}
    ,因为客户端关闭了套接字
  • genu-tcp:recv/2
    返回
    {error,closed}
  • 进程正常退出
  • 因此,实际上,您的echo服务器运行正常,但是@zxq9的评论中提到了一些可以改进的地方

    改进1 将接受套接字的控制权移交给新生成的进程

    -模块(echo)。
    -导出([listen/1])。
    -定义(TCP_选项,[二进制,{packet,0},{active,false},{reuseaddr,true}])。
    监听(端口)->
    {ok,LSocket}=gen_tcp:listen(端口,tcp_选项),
    接受(LSocket)。
    接受(LSocket)->
    {ok,CSocket}=gen_tcp:accept(LSocket),
    Ref=make_Ref(),
    To=spawn(fun()->init(Ref,CSocket)end),
    gen_tcp:控制进程(CSocket,To),
    到{切换,Ref,CSocket},
    接受(LSocket)。
    初始化(参考,套接字)->
    接收
    {切换,Ref,Socket}->
    {ok,Peername}=inet:Peername(套接字),
    io:格式(“[S]peername~p~n”,[peername]),
    回路(插座)
    结束。
    循环(套接字)->
    案例gen_tcp:recv(套接字,0)的
    {好的,数据}->
    io:格式(“[S]得到~p~n”,[Data]),
    gen_tcp:发送(套接字、数据),
    回路(插座);
    {错误,已关闭}->
    io:格式(“[S]闭合~n”,[]);
    E->
    io:格式(“[S]错误~p~n”,[E])
    结束。
    
    改进2 在关闭套接字之前,在客户端等待echo服务器发回数据

    spawn(乐趣()->
    {ok,Socket}=gen_tcp:connect(“127.0.0.1”,1111,[binary,{packet,0},{active,false}]),
    {ok,Peername}=inet:Peername(套接字),
    io:格式(“[C]对等名称~p~n”,[peername]),
    gen_tcp:发送(套接字,),
    案例gen_tcp:recv(套接字,0)的
    {好的,数据}->
    io:格式(“[C]得到~p~n”,[Data]),
    gen_tcp:关闭(插座);
    {错误,已关闭}->
    io:格式(“[C]闭合~n”,[]);
    E->
    io:格式(“[C]错误~p~n”,[E])
    结束
    (完)。
    
    例子 服务器应如下所示:

    1>c(echo)。
    {好的,回声}
    2> 回声:听(1111)。
    [S] peername{{127,0,0,1},57586}
    [S] 得到
    [S] 封闭的
    
    客户机应该是这样的:

    1>%粘贴来自改进2的代码
    [C] peername{{127,0,0,1},1111}
    [C] 得到
    
    建议 正如@zxq9所提到的,这不是OTP风格的代码,可能不应该用于教育目的以外的任何用途


    更好的方法可能是在服务器端侦听和接受连接时使用类似或的内容。这两个项目都有echo服务器的示例:和。

    您的套接字从不侦听,因此您不会收到消息。您还可以在发生任何事情之前立即从客户端关闭连接。控件也不会传递给套接字上的新进程。这里有一种不同的方法:在这种情况下,控制权最终被移交给了这个:注意,这不是OTP风格的代码,只是原始的Erlang。感谢您的帮助。我有一个问题,在客户端关闭套接字10秒后循环睡眠,为什么peername仍然第一次工作?我认为这取决于
    inet\u描述符->state
    标志以及
    inet\u描述符->peer\u ptr
    存储的套接字地址(请参阅)。如果缓冲区中有数据,即使客户端已经关闭了连接,套接字看起来仍然被认为是活动的。一旦数据被读取,套接字很快就会被标记为非活动和关闭。谢谢,我会看一看。