2

I am trying to receive data at client side, but nothing is received.

Server code that sends message

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.

Client that should receive and print out

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

1 回答 1

9

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

{packet, 2}

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

{packet, 0}

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).

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

get_msg(Socket, Chunks) ->
    Chunk = gen_tcp:recv(Socket, 0),
    get_msg(Socket, [Chunk|Chunks]).

那么问题就变成了:你怎么知道你什么时候阅读了整条消息,以便结束循环? {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.

但这引发了另一个问题:如果聊天服务器在 recv() 上循环等待来自客户端的消息,并且在客户端向服务器发送消息后,客户端在 recv() 上循环等待来自服务器的消息,并且双方都需要另一方关闭套接字以打破他们的 recv() 循环,那么你将陷入僵局,因为双方都没有关闭他们的套接字。结果,客户端必须关闭套接字才能让聊天服务器跳出它的 recv() 循环并处理消息。但是,由于客户端关闭了套接字,因此服务器无法将任何内容发送回客户端。{packet,0}结果,我不知道您是否可以在指定时进行双向通信。

以下是我关于阅读文档和搜索的{packet, N}结论:{active, true|false}


发送()

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

* 在“Erlang 编程(第 2 期)”中,第 3 页。176它说由于操作系统缓冲数据的方式,当您调用时会有少量数据被推送到目的地send(),并且reafersend()将阻塞直到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}]}

您可以使用 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       |
                   +--------------+----------------+     

{主动,假}

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

{数据包,1|2|4}

数据包元组指定每一方期望消息符合 的协议。{packet, 2}指定每条消息前面都有两个字节,其中包含消息的长度。这样,消息的接收者将知道要从字节流中读取多长时间才能到达消息的末尾。当您通过 TCP 连接发送消息时,您不知道该消息将被分成多少块。如果接收者在一个块之后停止读取,它可能没有读取整个消息。因此,接收者需要一个指示器来告诉它何时阅读了整条消息。

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

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

{数据包,0 | 原始}(等效):

{packet, 0}指定时,recv()将读取其 Length 参数指定的字节数。如果 Length 为 0,那么我认为recv()将从流中读取一个块,这将是任意数量的字节。因此,{packet, 0}and的组合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.

请注意,发送者不能简单地调用gen_tcp:close(Socket)来表示它已完成发送数据(请参阅文档中gen_tcp:close/1的描述)。相反,发送者必须通过调用gen_tcp:shutdown/2来表示已完成发送数据。

我认为聊天服务器有问题,因为它与以下内容{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

但它没有为recv().


{活跃,真实}

通过 TCP(或 UDP)连接发送的消息会自动为您从套接字读取并放入控制进程的邮箱中。控制进程是调用accept()的进程或调用connect()的进程。不是调用 recv() 直接从套接字读取消息,而是使用接收块从邮箱中提取消息:

get_msg(Socket)
    receive
        {tcp, Socket, Chunk} ->  %Socket is already bound!
        ...
    end

{数据包,1|2|4}

Erlang 会自动为您从套接字读取消息的所有块,并在邮箱中放置一条完整的消息(去掉长度标头):

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

{数据包,0 | 原始}(等效):

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

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


recv() 文档提到了一些关于recv()Length 参数适用于原始模式的套接字的内容。但是因为我不知道 Socket 何时处于原始模式,所以我不相信 Length 参数。但请看这里:Erlang gen_tcp:recv(Socket, Length) semantics。好的,现在我到了某个地方:来自 erlang inet docs

{packet, PacketType}(TCP/IP sockets)
Defines the type of packets to use for a socket. Possible values:

raw | 0
    No packaging is done.

1 | 2 | 4
    Packets consist of a header specifying the number of bytes in the packet, followed by that  
    number of bytes. The header length can be one, two, or four bytes, and containing an  
    unsigned integer in big-endian byte order. Each send operation generates the header, and the  
    header is stripped off on each receive operation.

    The 4-byte header is limited to 2Gb [message length].

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

于 2017-05-13T23:43:24.847 回答