10

我正在使用 berkeley 套接字和 TCP(SOCK_STREAM 套接字)。

过程是:

  1. 我连接到一个远程地址。
  2. 我给它发信息。
  3. 我从它那里收到一条消息。

想象一下我正在使用以下缓冲区:

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);

问题是:

  • 我如何知道在第一次调用 recv 后读取缓冲区是否为空?如果它不为空,我将不得不再次调用 recv,但如果我在它为空时这样做,我会让它阻塞很长时间。
  • 我如何知道我读入了 recv_buffer 的字节数?我不能使用 strlen 因为我收到的消息可能包含空字节。

谢谢。

4

4 回答 4

12

我如何知道在第一次调用 recv 后读取缓冲区是否为空?如果它不为空,我将不得不再次调用 recv,但如果我在它为空时这样做,我会让它阻塞很长时间。

您可以使用selectpoll系统调用以及您的套接字描述符来判断是否有数据等待从套接字读取。

但是,通常应该有一个发送方和接收方都遵循的商定协议,以便双方都知道要传输多少数据。例如,可能发送方首先发送一个 2 字节整数,指示它将发送的字节数。然后接收器首先读取这个 2 字节整数,以便它知道要从套接字读取多少字节。

无论如何,正如托尼在下面指出的那样,一个健壮的应用程序应该在标头中使用长度信息的组合,并在每次调用之前结合轮询套接字以获取额外的数据recv,(或使用非阻塞套接字)。这将防止您的应用程序在以下情况下阻塞,例如,您知道(从标头中)应该还有 100 个字节要读取,但是对等方由于任何原因未能发送数据(可能对等计算机是意外关闭),从而导致您的recv呼叫被阻止。

我如何知道我读入了 recv_buffer 的字节数?我不能使用 strlen 因为我收到的消息可能包含空字节。

recv系统调用将返回读取的字节数,如果发生错误,则返回 -1 。

从 recv(2) 的手册页:

[recv] 返回接收到的字节数,如果发生错误,则返回 -1。当对端执行了有序关闭时,返回值将为 0。

于 2010-12-06T01:41:26.757 回答
2

我如何知道在第一次调用 recv 后读取缓冲区是否为空?

即使是第一次(在接受客户端之后),如果客户端连接丢失,recv 也会阻塞并失败。您必须:

  • 使用selectpoll(BSD 套接字)或某些特定于操作系统的等效项,它可以告诉您特定套接字描述符上是否有可用数据(以及异常条件和可以写入更多输出的缓冲区空间)
  • 您可以将套接字设置为非阻塞,这样recv只会返回立即可用的内容(可能没有)
  • 您可以创建一个线程,您可以负担得起阻塞recv数据,知道其他线程将执行您关心的其他工作以继续

我如何知道我读入了 recv_buffer 的字节数?我不能使用 strlen 因为我收到的消息可能包含空字节。

recv()返回读取的字节数,或 -1 错误。

请注意,TCP 是一种字节流协议,这意味着您只能保证能够以正确的顺序从中读取和写入字节,但不能保证保留消息边界。因此,即使发送方对他们的套接字进行了一次大的单次写入,它也可以在途中被分段并以几个较小的块到达,或者可以通过一个/合并和检索几个较小的send()/ s 。write()recv()read()

出于这个原因,请确保您循环调用recv,直到您获得所需的所有数据(即您可以处理的完整逻辑消息)或出现错误。您应该准备/能够处理send从您的客户端获取部分/所有后续 s(如果您没有协议,其中每一方仅在从另一方获得完整消息后发送,并且不使用具有消息长度的标头) . 请注意,对消息头(带有长度)进行recvs,然后正文可能会导致更多的调用recv(),从而对性能产生潜在的不利影响。

这些可靠性问题经常被忽略。在单个主机、可靠且快速的 LAN、涉及较少的路由器和交换机以及较少或非并发消息的情况下,它们的表现较少。然后它们可能会在负载和更复杂的网络上中断。

于 2010-12-06T02:13:23.847 回答
0
  1. 如果recv()返回的字节数少于 3000,则可以假定读取缓冲区为空。如果它在您的 3000 字节缓冲区中返回 3000 字节,那么您最好知道是否继续。大多数协议都包含一些关于 TLV 的变体——类型、长度、值。每条消息都包含消息类型的指示符、一些长度(如果长度固定,则可能由类型暗示)和值。如果在阅读您确实收到的数据时,您发现最后一个单元不完整,您可以假设还有更多内容需要阅读。也可以将socket做成非阻塞socket;recv()如果没有读取数据,则 EAGAIN 或 EWOULDBLOCK 将失败。

  2. recv()函数返回读取的字节数。

于 2010-12-06T01:44:54.653 回答
0

带有 FIONREAD 选项的 ioctl() 告诉您当前可以在不阻塞的情况下读取多少数据。

于 2011-02-13T03:04:40.713 回答