Erlang gen_tcp没有收到任何东西

Mar*_*aht 1 erlang

试图在客户端接收数据,但是什么也没收到。

发送消息的服务器代码

client(Socket, Server) ->
  gen_tcp:send(Socket,"Please enter your name"),
  io:format("Sent confirmation"),
  {ok, N} = gen_tcp:recv(Socket,0),
  case string:tokens(N,"\r\n") of
    [Name] ->
      Client = #client{socket=Socket, name=Name, pid=self()},
      Server ! {'new client', Client},
      client_loop(Client, Server)
  end.
Run Code Online (Sandbox Code Playgroud)

应接收并打印的客户

    client(Port)->
  {ok, Sock} = gen_tcp:connect("localhost",Port,[{active,false},{packet,2}]),
  A = gen_tcp:recv(Sock,0),
  A.
Run Code Online (Sandbox Code Playgroud)

7st*_*tud 5

我认为您的客户有误,因为它指定:

{packet, 2}
Run Code Online (Sandbox Code Playgroud)

服务器尚未指定(未显示代码):

{packet, 0}
Run Code Online (Sandbox Code Playgroud)

在Programming Erlang (2nd)第 269它说:

请注意,客户端和服务器使用的 数据包参数必须一致。如果使用打开服务器,使用打开{packet,2}客户端{packet,4},则将无法进行任何操作。

以下客户端可以从服务器成功接收文本:

%%=== Server:  {active,false}, {packet,0} ====

client(Port) ->
    {ok, Socket} = gen_tcp:connect(
        localhost, 
        Port, 
        [{active,false},{packet,0}]
     ),

    {ok, Chunk} = gen_tcp:recv(Socket, 0),
    io:format("Client received: ~s", [Chunk]),
    timer:sleep(1000),

    Name = "Marko",
    io:format("Client sending: ~s~n", [Name]),
    gen_tcp:send(Socket, Name),

    loop(Socket).

loop(Socket) ->
    {ok, Chunk} = gen_tcp:recv(Socket, 0),
    io:format("Client received: ~s~n", [Chunk]),
    loop(Socket).
Run Code Online (Sandbox Code Playgroud)

但是,我认为chatserver和我的客户都遇到严重问题。通过TCP(或UDP)连接发送消息时,必须假定消息将被分成不确定数量的块-每个块具有任意长度。当{packet,0}指定时,我认为recv(Socket, 0)只会从套接字读取一个块,然后返回。该块可能是整个消息,也可能只是消息的一部分。为了确保您已经从套接字读取了整个消息,我认为您必须遍历recv():

get_msg(Socket, Chunks) ->
    Chunk = gen_tcp:recv(Socket, 0),
    get_msg(Socket, [Chunk|Chunks]).
Run Code Online (Sandbox Code Playgroud)

然后问题就变成了:您如何知道阅读完整个消息后才能结束循环的? {packet,0}告诉Erlang不要在消息前添加长度标头,那么您如何知道消息的结尾呢?还会有更多的块,还是recv()已经读取了最后一个块?我认为消息结尾的标记是另一端关闭套接字时:

get_msg(Socket, Chunks) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Chunk} ->
            get_msg(Socket, [Chunk|Chunks]);
        {error, closed} ->
            lists:reverse(Chunks);
        {error, Other} ->
            Other
    end.
Run Code Online (Sandbox Code Playgroud)

但是,这又引发了另一个问题:如果chatserver在等待来自客户端的消息的recv()上循环,并且在客户端向服务器发送消息后,客户端会在recv()上循环以等待来自服务器的消息,而且双方都需要另一端关闭套接字以退出它们的recv()循环,然后您将陷入僵局,因为双方都没有关闭其套接字。结果,客户端将必须关闭套接字,以使chatserver脱离其recv()循环并处理消息。但是,由于客户端关闭了套接字,因此服务器无法将任何内容发送回客户端。结果,我不知道{packet,0}指定的时间是否可以进行双向通讯。

这里是我约的结论{packet, N},并{active, true|false}从阅读文档和周围搜索:


send():

当您呼叫时send(),实际上没有数据*传输到目的地。而是send()阻塞直到目标调用recv(),然后才将数据传输到目标。

*在第22页的“编程Erlang(第二个)”中。176它表示send()由于操作系统缓冲数据的方式,当您调用时,少量数据将被推送到目标,并且reafer send()将阻塞直到recv()将数据提取到目标。


默认选项:

您可以通过为套接字指定一个空列表来获取套接字的默认值,然后执行以下操作:

Defaults = inet:getopts(Socket, [mode, active, packet]),
io:format("Default options: ~w~n", [Defaults]).

--output:--
Default options: {ok,[{mode,list},{active,true},{packet,0}]}
Run Code Online (Sandbox Code Playgroud)

您可以使用inet:getopts()来显示gen_tcp:accept(Socket)返回的套接字具有与Socket相同的选项。


                    {active, true}   {active,false}
                   +--------------+----------------+
{packet, 1|2|4}:   |    receive   |     recv()     |
                   |    no loop   |     no loop    |
                   +--------------+----------------+
{packet, 0|raw}:   |    receive   |     recv()     |
  (equivalent)     |    loop      |     loop       |
                   +--------------+----------------+     
Run Code Online (Sandbox Code Playgroud)

{active,false}

邮件不会降落在邮箱中。此选项用于防止客户端将邮件泛洪到服务器的邮箱中。不要尝试使用接收块从邮箱中提取“ tcp”消息-不会有任何消息。当进程想要读取消息时,该进程需要通过调用直接从套接字读取消息recv()。

{packet,1 | 2 | 4}:

数据包元组指定双方希望消息遵循的协议。 {packet, 2}指定每个消息将以两个字节开头,其中将包含消息的长度。这样,消息的接收者将知道从字节流继续读取多长时间才能到达消息的末尾。通过TCP连接发送消息时,您不知道消息将被拆分成多少块。如果接收方在一个块之后停止读取,则可能尚未读取整个消息。因此,接收者需要一个指示器来告诉它何时已读取整个消息。

使用{packet, 2},接收方将读取两个字节以获取消息的长度(例如100),然后接收方将等待,直到已从流到接收方的随机大小的字节块中读取了100个字节。

请注意,当您调用时send(),erlang自动计算消息中的字节数,并将长度插入N字节中(由指定){packet, N},然后追加消息。同样,当您调用recv()erlang 时N,如所指定{packet, N},自动从流中读取字节以获取消息的长度,然后进行recv()阻塞,直到它从套接字读取长度字节为止,然后recv()返回整个消息。

{packet,0 | raw}(等效):

当{packet, 0}被指定,recv()将读取其长度参数指定的字节数。如果Length为0,那么我认为recv()将从流中读取一个块,该块将是任意数量的字节。其结果是,组合{packet, 0}并recv(Socket, 0)要求您创建一个循环读取消息的所有块,并指示对recv()停止阅读,因为它已经达到了消息的末尾,当对方关闭套接字将是:

get_msg(Socket, Chunks) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Chunk} -> 
            get_msg(Socket, [Chunk|Chunks]);
        {error, closed} ->
            lists:reverse(Chunks);
        {error, Other} ->
            Other
    end.
Run Code Online (Sandbox Code Playgroud)

请注意,发件人不能简单地调用gen_tcp:close(Socket)信号来表示已完成发送数据(请参阅文档中的gen_tcp:close / 1的描述)。取而代之的是,发送者必须通过调用gen_tcp:shutdown / 2来发出发送数据的信号。

我认为chatserver是错误的,因为它{packet, 0}与结合使用指定recv(Socket, 0):

client_handler(Sock, Server) ->
    gen_tcp:send(Sock, "Please respond with a sensible name.\r\n"),
    {ok,N} = gen_tcp:recv(Sock,0), %% <**** HERE ****
    case string:tokens(N,"\r\n") of
Run Code Online (Sandbox Code Playgroud)

但是它不使用循环recv()。


{active,true}

通过TCP(或UDP)连接发送的消息会自动从套接字读取,并放置在控制进程的邮箱中。控制过程是称为accept()的过程或称为connect()的过程。无需调用recv()直接从套接字读取消息,而是使用具有receive块的方法从邮箱中提取消息:

get_msg(Socket)
    receive
        {tcp, Socket, Chunk} ->  %Socket is already bound!
        ...
    end
Run Code Online (Sandbox Code Playgroud)

{packet,1 | 2 | 4}:

Erlang自动为您从套接字读取消息的所有块,并将完整的消息(剥去长度标题)放入邮箱:

get_msg(Socket) ->
    receive
        {tcp, Socket, CompleteMsg} ->
            CompleteMsg,
        {tcp_closed, Socket} ->
            io:format("Server closed socket.~n")
    end.
Run Code Online (Sandbox Code Playgroud)

{packet,0 | raw}(等效):

消息没有长度标头,因此当Erlang从套接字读取时,Erlang无法知道消息的末尾何时到达。结果,Erlang将它从套接字读取的每个块放入邮箱中。您需要一个循环来从邮箱中提取所有块,并且另一端必须关闭套接字以发出不再有块的信号:

get_msg(ClientSocket, Chunks) ->
    receive
        {tcp, ClientSocket, Chunk} ->
            get_msg(ClientSocket, [Chunk|Chunks]);
        {tcp_closed, ClientSocket} ->
            lists:reverse(Chunks)
    end.
Run Code Online (Sandbox Code Playgroud)

该recv() 文档提到了有关recv()Length参数的内容,仅适用于原始模式下的套接字。但是因为我不知道Socket何时处于原始模式,所以我不相信Length参数。但请参见此处:Erlang gen_tcp:recv(Socket,Length)语义。好的,现在到了:从erlang inet docs:

{packet, 2}
Run Code Online (Sandbox Code Playgroud)

如Erlang gen_tcp:recv(Socket,Length)语义中的示例所确认,当{packet,0}指定时,recv()可以指定从TCP流读取的Length。