关于 recv 和读取缓冲区 - C Berkeley Sockets
Posted
技术标签:
【中文标题】关于 recv 和读取缓冲区 - C Berkeley Sockets【英文标题】:About recv and the read buffer - C Berkeley Sockets 【发布时间】:2011-05-20 17:41:25 【问题描述】:我正在使用 berkeley 套接字和 TCP(SOCK_STREAM 套接字)。
流程是:
-
我连接到远程地址。
我向它发送消息。
我收到了一条消息。
假设我正在使用以下缓冲区:
char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
问题是:
我如何知道第一次调用 recv 后读取缓冲区是否为空?如果它不为空,我将不得不再次调用 recv,但如果我在它为空时这样做,我会让它阻塞很长时间。 如何知道我读入了recv_buffer 的字节数?我无法使用 strlen,因为我收到的消息可能包含空字节。谢谢。
【问题讨论】:
【参考方案1】:我怎么知道调用recv后是否 第一次读取缓冲区为空或 不是?如果它不是空的,我将不得不 再次调用recv,但如果我这样做时 它是空的,我会阻止它 很长一段时间。
您可以使用select
或poll
系统调用以及您的套接字描述符来判断是否有数据等待从套接字读取。
但是,通常应该有一个发送方和接收方都遵循的商定协议,以便双方都知道要传输多少数据。例如,发送者可能首先发送一个 2 字节整数,指示它将发送的字节数。然后接收方首先读取这个 2 字节的整数,以便知道还要从套接字读取多少字节。
无论如何,正如托尼在下面指出的那样,一个健壮的应用程序应该在标头中使用长度信息的组合,并在每次调用 recv
之前轮询套接字以获取其他数据,(或使用非阻塞套接字)。这将防止您的应用程序在以下情况下阻塞,例如,您知道(从标头中)应该还有 100 个字节要读取,但是对等方由于任何原因未能发送数据(可能对等计算机是意外关闭),从而导致您的 recv
呼叫被阻止。
我怎么知道我有多少字节 读入recv_buffer?我无法使用 strlen 因为我收到的消息 可以包含空字节。
recv
系统调用将返回读取的字节数,如果发生错误,则返回 -1。
来自 recv(2) 的手册页:
[recv] 返回字节数 如果发生错误,则为 -1。 返回值为 0 时 同行有条不紊地执行了 关机。
【讨论】:
read(2)
手册页与recv(2)
有什么关系?他们说了类似的话,但引用相关页面会更好。
@Jonathan,当描述符类型是套接字时,read
与recv
相同,除了recv
允许额外的标志参数。但我编辑了我的答案以使用recv
以避免混淆。
只是一个挑剔的,可能是意想不到的含义:“选择/轮询/但是标头中的消息长度”错误地暗示此类标头解决了阻塞问题,其中选择/轮询,非阻塞套接字或线程应与消息长度标头或标记数据结合使用。【参考方案2】:
如果 recv()
返回的字节数少于 3000,那么您可以假设读取缓冲区为空。如果它在您的 3000 字节缓冲区中返回 3000 字节,那么您最好知道是否继续。大多数协议都包含一些关于 TLV 的变体——类型、长度、值。每条消息都包含消息类型的指示符、一些长度(如果长度固定,则可能由类型暗示)和值。如果在阅读您收到的数据时,您发现最后一个单元不完整,您可以假设还有更多内容需要阅读。也可以将socket做成非阻塞socket;如果没有读取数据,recv()
将失败并显示 EAGAIN 或 EWOULDBLOCK。
recv()
函数返回读取的字节数。
【讨论】:
不正确。您可以假设接收缓冲区已被该读取清空,但您不能假设在您下次准备调用recv() 时数据还没有随后到达。跨度> @EJP:“错误”是一个非常强烈的声明——在我看来,很明显你无法判断自从你的recv()
调用以来数据是否已经到达,但也许是那个基本水平,明显的陈述确实需要指出。【参考方案3】:
第一次调用recv后如何知道读取缓冲区是否为空?
即使是第一次(接受客户端后),如果客户端连接丢失,recv 也会阻塞并失败。您必须:
使用select
或poll
(BSD 套接字)或某些特定于操作系统的等效项,它们可以告诉您特定套接字描述符上是否有可用数据(以及异常条件和缓冲区空间,您可以写入更多输出)到)
您可以将套接字设置为非阻塞,这样recv
将只返回立即可用的任何内容(可能没有)
您可以创建一个线程,让您有能力阻止 recv
-ing 数据,因为知道其他线程将执行您关心的其他工作以继续进行
我如何知道我读入了recv_buffer 的字节数?我不能使用 strlen 因为我收到的消息可能包含空字节。
recv()
返回读取的字节数,错误时返回 -1。
请注意,TCP 是一个 字节流 协议,这意味着您只能保证能够以正确的顺序从中读取和写入字节,但不能保证消息边界被保存。因此,即使发送者对他们的套接字进行了一次大的单次写入,它也可能在途中被分段并以几个较小的块到达,或者几个较小的 send()
/write()
s 可以合并并由一个 recv()
检索/read()
.
因此,请确保您循环调用 recv
,直到您获得所需的所有数据(即您可以处理的完整逻辑消息)或出现错误。您应该准备/能够处理从您的客户端获取部分/全部后续send
s(如果您没有协议,其中每一方仅在从另一方获得完整消息后发送,并且不使用标头消息长度)。请注意,对消息头(带长度)进行recvs 后,正文可能会导致对recv()
的更多调用,从而对性能产生潜在的不利影响。
这些可靠性问题经常被忽略。在单个主机、可靠且快速的 LAN、涉及较少的路由器和交换机以及较少或非并发消息的情况下,它们的表现较少。然后它们可能会在负载和更复杂的网络上崩溃。
【讨论】:
【参考方案4】:带有 FIONREAD 选项的 ioctl() 告诉您当前可以在不阻塞的情况下读取多少数据。
【讨论】:
ioctl()
函数实际上并不是 POSIX 标准的一部分,尽管它在单一 UNIX 规范的 STREAMS 部分中显示为一个过时的接口(参见 ioctl()。事实上在大多数 UNIX 衍生平台上都可用,但它是特定于平台的。
@Jonathan Leffler:同意(不是 OP 提到 POSIX)。 FIONREAD 或其变体得到了足够广泛的支持,Java 可以在其所有平台上的套接字上提供 available()。以上是关于关于 recv 和读取缓冲区 - C Berkeley Sockets的主要内容,如果未能解决你的问题,请参考以下文章