使用gen_tcp的Erlang客户端-服务器示例未收到任何信息 [英] Erlang client-server example using gen_tcp is not receiving anything

查看:68
本文介绍了使用gen_tcp的Erlang客户端-服务器示例未收到任何信息的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

发送消息的服务器代码

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.


推荐答案

我认为您的客户错误,因为它指定了以下内容:

I think your client is faulty because it specifies:

{packet, 2}

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

yet the server specifies (in code not shown) :

{packet, 0}

编程Erlang(第2个)在第269表示:


请注意,客户端和服务器
使用 packet 的参数必须同意。如果使用 {packet,2} 打开服务器,使用 {packet,4} 打开客户端,则不会

Note that the arguments to packet used by the client and the server must agree. If the server was opened with {packet,2} and the client with {packet,4}, then nothing would work.

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

The following client can successfully receive text from the server:

%%=== 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():

However, I think that both the chatserver and my client have serious issues. When you send a message through a TCP (or UDP) connection, you have to assume that the message will get split into an indeterminate number of chunks--each with an arbitrary length. When {packet,0} is specified, I think recv(Socket, 0) will only read one chunk from the socket, then return. That chunk may be the entire message, or it might be only a piece of the message. To guarantee that you've read the entire message from the socket, I think you have to loop over the recv():

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

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

Then the question becomes: how do you know when you've read the entire message so that you can end the loop? {packet,0} tells Erlang not to prepend a length header to a message, so how do you know where the end of the message is? Are more chunks coming, or did the recv() already read the last chunk? I think the marker for the end of the message is when the other side closes the socket:

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.

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

But that raises another issue: if the chatserver is looping on a recv() waiting for a message from the client, and after the client sends a message to the server the client loops on a recv() waiting for a message from the server, and both sides need the other side to close the socket to break out of their recv() loops, then you will get deadlock because neither side is closing their socket. As a result, the client will have to close the socket in order for the chatserver to break out of its recv() loop and process the message. But, then the server can't send() anything back to the client because the client closed the socket. As a result, I don't know if you can do two way communication when {packet,0} is specified.

以下是我对 {packet,N} {active,true | false} 的结论文档并在附近搜索:

Here are my conclusions about {packet, N} and {active, true|false} from reading the docs and searching around:

send()

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

When you call send(), no data* is actually transferred to the destination. Instead, send() blocks until the destination calls recv(), and only then is data transferred to the destination.

*在第2页的编程Erlang(第二个)中。 176它表示由于操作系统缓冲数据的方式,当您调用 send()时,少量数据将被推送到目标位置,从而增加了 send()将一直阻塞,直到 recv()将数据拉到目的地为止。

* In "Programming Erlang (2nd)", on p. 176 it says that a small amount of data will be pushed to the destination when you call send() due to the way an OS buffers data, and thereafer send() will block until a recv() pulls data to the destination.

默认选项

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

You can get the defaults for a socket by specifying an empty list for its options, then doing:

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具有相同选项的套接字。

You can use inet:getopts() to show that gen_tcp:accept(Socket) returns a socket with the same options as 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}


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

Messages do not land in the mailbox. This option is used to prevent clients from flooding a server's mailbox with messages. Do not try to use a receive block to extract 'tcp' messages from the mailbox--there won't be any. When a process wants to read a message, the process needs to read the message directly from the socket by calling recv().

{packet,1 | 2 | 4}

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

The packet tuple specifies the protocol that each side expects messages to conform to. {packet, 2} specifies that each message will be preceded by two bytes, which will contain the length of the message. That way, a receiver of a message will know how long to keep reading from the stream of bytes to reach the end of the message. When you send a message over a TCP connection, you have no idea how many chunks the message will get split into. If the receiver stops reading after one chunk, it might not have read the whole message. Therefore, the receiver needs an indicator to tell it when the whole message has been read.

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

With {packet, 2}, a receiver will read two bytes to get the length of the message, say 100, then the receiver will wait until it has read 100 bytes from the randomly sized chunks of bytes that are streaming to the receiver.

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

Note that when you call send(), erlang automatically calculates the number of bytes in the message and inserts the length into N bytes, as specified by {packet, N}, and appends the message. Likewise, when you call recv() erlang automatically reads N bytes from the stream, as specified by {packet, N}, to get the length of the message, then recv() blocks until it reads length bytes from the socket, then recv() returns the whole message.

{packet,0 |原始} (等效):

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

When {packet, 0} is specified, recv() will read the number of bytes specified by its Length argument. If Length is 0, then I think recv() will read one chunk from the stream, which will be an arbitrary number of bytes. As a result, the combination of {packet, 0} and recv(Socket, 0) requires that you create a loop to read all the chunks of a message, and the indicator for recv() to stop reading because it has reached the end of the message will be when the other side closes the socket:

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

Note that a sender cannot simply call gen_tcp:close(Socket) to signal that it is done sending data (see the description of gen_tcp:close/1 in the docs). Instead, a sender has to signal that is is done sending data by calling gen_tcp:shutdown/2.

我认为聊天服务器有问题,因为它在其中指定了 {packet,0} recv(Socket,0)组合:

I think the chatserver is faulty because it specifies {packet, 0} in combination with 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()使用循环。

yet it does not use a loop for the recv().


{active,true}


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

Messages sent through a TCP (or UDP) connection are automatically read from the socket for you and placed in the controlling process's mailbox. The controlling process is the process that called accept() or the process that called connect(). Instead of calling recv() to read messages directly from the socket, you extract messages from the mailbox with a receive block:

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

{packet,1 | 2 | 4}

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

Erlang automatically reads all the chunks of a message from the socket for you and places a complete message (with the length header stripped off) in the mailbox:

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

{packet,0 |原始} (等效):

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

Messages will not have a length header, so when Erlang reads from the socket, Erlang has no way of knowing when the end of the message has arrived. As a result, Erlang places each chunk it reads from the socket into the mailbox. You need a loop to extract all the chunks from the mailbox, and the other side has to close the socket to signal that no more chunks are coming:

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









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



The recv() docs mention something about recv()'s Length argument only being applicable to sockets in raw mode. But because I don't know when a Socket is in raw mode, I don't trust the Length argument. But see here: Erlang gen_tcp:recv(Socket, Length) semantics. Okay, now I'm getting somewhere: from the 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].

确认,当指定 {packet,0} 时, recv()可以指定要从TCP流读取的长度。

As the examples at Erlang gen_tcp:recv(Socket, Length) semantics confirm, when {packet,0} is specified, a recv() can specify the Length to read from the TCP stream.

这篇关于使用gen_tcp的Erlang客户端-服务器示例未收到任何信息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆