使用gen_tcp的Erlang客户端-服务器示例未接收任何内容

ndasle7k  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(210)

我尝试在客户端接收数据,但没有收到任何数据。
发送消息的服务器代码

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

eulz3vhy1#

I think your client is faulty because it specifies:

{packet, 2}

yet the server specifies (in code not shown) :

{packet, 0}

In Programming Erlang (2nd) on p. 269 it says:
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).

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

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.

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.
Here are my conclusions about {packet, N} and {active, true|false} from reading the docs and searching around:

send():

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.

  • 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.
    Default options:

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}]}

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}

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}:

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.
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.
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 | raw} (equivalent):

当指定{packet, 0}时,recv()将读取其Length参数指定的字节数。如果Length为0,则我认为recv()将从流中读取一个块,这将是任意字节数。因此,{packet, 0}recv(Socket, 0)的组合要求您创建一个循环来读取消息的所有块,并且x1M32N1X因为它已经到达消息的结尾而停止读取的指示符将是当另一侧关闭套接字时:

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)的组合,但它没有为recv()使用循环:

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

{活动,真}

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

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| 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()文档提到了recv()的Length参数 * only * 适用于 * raw * 模式的套接字。但是因为我不知道套接字何时处于raw模式,所以我不相信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流读取的Length。

相关问题