如何判断套接字缓冲区是否已满?

时间:2009-11-26 11:43:28

标签: c sockets

如何判断读取套接字缓冲区是否已满或写入套接字缓冲区是否为空?

有没有办法可以在没有系统调用的情况下获得套接字缓冲区的状态?

更新:怎么样:当读取套接字缓冲区已满或写入套接字缓冲区为空时,我想获得回调或信号。通过这种方式,我可以停止处理以允许在线路上发生更多I / O,因为在线路上发送数据时,I / O限制总是一个问题。

select()调用是如何检查读缓冲区中是否包含某些内容的。 当它已满(我认为)。

8 个答案:

答案 0 :(得分:12)

使用select和零超时轮询文件描述符 - 如果select表示它是可写的,则发送缓冲区未满。

(哦...... 没有系统调用。不,没有。)

<强>附录:

为了回答您更新的问题,您可以在TCP套接字上使用两个ioctlSIOCINQ返回接收缓冲区中未读数据的数量,SIOCOUTQ返回金额发送队列中的未发送数据。我不相信这些会有任何异步事件通知,这将让你不得不轮询。

答案 1 :(得分:12)

我知道这是一个旧帖子,但是为了那些通过搜索引擎偶然发现这个问题的人,我会回答这个问题,因为它上面没有真正回答。

在开始之前,克服系统调用挂断 - 在不切换内核空间的情况下,无法与基于内核的(* nix)网络堆栈进行交互。您的目标应该是了解堆栈功能,以便您可以充分利用系统。

  

如何判断读取套接字缓冲区是否已满

这部分已经回答了 - 你没有回答,因为这不是你应该怎么想的。

如果发送方(严重)将其分段为TCP帧(通常是由于未在输出上缓冲封送数据,并且使用TCP_NDELAY关闭Nagle算法),那么减少系统调用数量的想法是一个好处理念。您应该使用的方法涉及设置“低水位线”进行阅读。首先,通过使用setsockopt()设置SO_RCVBUF,建立您认为合理的接收缓冲区大小。然后使用getsockopt()读回实际的读取缓冲区大小,因为您可能无法得到您要求的内容。 :)不幸的是,并非所有实现都允许您再次阅读SO_RCVBUF,因此您的里程可能会有所不同。接下来,在您想要阅读之前,确定您想要阅读的数据量。使用setsockopt()设置此大小的SO_RCVLOWAT。现在,当至少读取的数据量时,套接字的文件描述符将只选择可读。

  

或写套接字缓冲区是空的?

这是一个有趣的,因为我最近需要这样做,以确保我的MODBUS / TCP ADU每个都占用了自己的TCP帧,这是MODBUS规范所要求的(@steve:控制碎片是你做的一次需要知道发送缓冲区何时为空!)。就原始海报而言,我非常怀疑他真的想要这个,并且相信在他开始之前了解发送缓冲区大小会更好,并且在发送期间定期检查发送缓冲区中的数据量,使用已经描述的技术。这将提供有关所用发送缓冲区比例的更细粒度的信息,可用于更顺利地节省生产。

对于那些仍然对如何检测(异步)发送缓冲区为空(一旦你确定它真的你想要的东西)感兴趣的人,答案很简单 - 你设置发送低-watermark(SO_SNDLOWAT)等于发送缓冲区大小。这样,套接字的文件描述符只会在发送缓冲区为空时选择为可写。

我对问题的回答主要围绕select()的使用并非巧合。 在几乎所有情况下(我意识到我现在正在进入宗教领域!)需要移动大量数据(内部和主机间)的应用程序最好构造为单线程状态机,使用选择掩码和基于pselect()的处理循环。如今,一些操作系统(Linux命名之一)甚至允许您使用文件描述符选择来管理信号处理。什么奢侈 - 当我还是个孩子的时候...... :)。

彼得

答案 2 :(得分:10)

您可以尝试ioctl。 FIONREAD告诉您可以立即读取多少字节。如果这与缓冲区大小(您可以通过另一个icotl调用检索和/或设置)相同,则缓冲区已满。同样,如果您可以写出与输出缓冲区大小一样多的字节,那么输出缓冲区为空。

我没有广泛支持FIONREAD,FIONWRITE和SIOCGIFBUFS(或等价物)。我不确定我曾经使用过任何一种,虽然我有一种偷偷摸摸的感觉,我出于某种原因在Symbian上使用了类似的功能。

调用是否需要内核模式来计算它是特定于平台的。模糊地试图避免系统调用并不是一种有效的优化技术。

一个基本的BSD风格的套接字接口没有提到任何关于读写缓冲区的内容。什么时候发送缓冲区是否为空是否重要?它当然并不意味着已经在套接字的另一个端点接收到所有数据 - 它可能位于某个路由器的某个地方。同样,“你的”读缓冲区已满,并不能保证另一端的写操作会阻塞。

一般来说,您只需尽可能多地读/写,并让套接字层处理复杂性。如果你看到很多I / O都是用小尺寸完成的,那么可能存在一些性能问题。但请记住,流套接字将一次发送/接收一个包,其中包含一个数据块。除非设置了TCP_NODELAY,否则不会像NIC上的字节到达字节,并且您最终可能每个字节进行一次读取调用。它们是以数据包形式到达的,因此很可能一次性可读,也许一次只能达到1k-ish。你不太可能通过推迟阅读来加快速度,直到有很多东西需要阅读。实际上,您可能会使情况变得更糟,因为当您的端点的读取缓冲区已满时,存在传入数据被丢弃的风险,因为无处存储它,导致延迟和重新发送。

答案 3 :(得分:3)

考虑到套接字的内核缓冲区存在于内核空间中,我怀疑在没有系统调用的情况下有任何方法可以要求大小。
使用系统调用,您可以尝试使用PEEK进行recv。

ret = recv(fd, buf, len, MSG_PEEK);

将在不清空缓冲区的情况下执行recv。

答案 4 :(得分:2)

如果没有系统调用,这是不可能的。但是系统调用有什么问题?

答案 5 :(得分:0)

我认为你的方法存在缺陷/注定的根本原因。系统不希望告诉您何时读取缓冲区已满/写入缓冲区为空,因为这些事件表明您与系统之间的合同出现故障。如果事情到达那一点(特别是在读取方向上),那么太晚可以确保协议栈的平稳运行。当您最终决定读取缓冲区时,可能会有更多数据到达。您应该在缓冲区之前读取缓冲区,这就是缓冲I / O的全部内容。

答案 6 :(得分:0)

如果你在单独的线程中读取()s,SO_RCVLOWAT可以帮助阻止此读取,直到缓冲区中有足够的数据。不幸的是,poll()和select()至少在Linux上忽略了这个套接字选项,并且总是检查可用的单字节。

答案 7 :(得分:0)

@blaze,

  

Linux和SO_RCVLOWAT

尊敬的是,我的经历与你的不同。 自从FC5以来,我一直在Linux中使用接收缓冲区低水位,在通过IP(UDP和TCP)分发视频的产品中,所以我理解充分利用网络堆栈功能是多么重要。事实上,Linux是第一个允许您回读低水印的实现之一(有些仍然不允许)。 :)

你提到poll()和select()是不尊重SO_RCVLOWAT。 只要我记得,我一直在使用pselect(),所以问题可能在于select()和poll()。在任何情况下,你应该总是使用pselect()或ppoll(),如果可用的话,优先于旧的调用,因为它们可以在您进入/离开调用时以原子方式改变程序的信号掩码。如果您了解这意味着什么,那么您将会理解为什么这在商业软件中至关重要。如果没有,这样的讨论将保证自己的线程。 :)

彼得