Erlang gen_tcp没有收到任何东西

时间:2017-05-13 19:25:11

标签: 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.

应该接收并打印的客户

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

1 个答案:

答案 0 :(得分:4)

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

{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(第二个)",在p。 176它表示,由于操作系统缓冲数据的方式,当您调用send()时会将少量数据推送到目标,并且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}]}

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

{active,false}

消息登陆邮箱。此选项用于防止客户端使用消息充斥服务器的邮箱。不要尝试使用接收块来提取&#t; 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参数指定的字节数。如果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.

请注意,发件人不能简单地致电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()使用循环。

  

{active,true}

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

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

{packet,1 | 2 | 4}

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

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

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

recv() docs提及recv()长度参数适用于 raw 中的套接字模式。但是因为我不知道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) semantics处的示例所确认的那样,当指定{packet,0}时,recv()可以指定从TCP流中读取的长度。