如果应用程序可以确保套接字的发送缓冲区中始终有空间,那么阻塞和非阻塞发送是否具有相同的性能?在这种情况下,这两种方法比另一种方法有什么优势吗?
3 回答
阻塞和非阻塞的唯一区别send
是内核是让你的进程进入睡眠还是返回EWOULDBLOCK
。所以就性能而言,应该没有区别。
但是,我怀疑您的隐含假设,即发送不能仅仅因为发送缓冲区有可用空间而阻塞。想象一下系统上的一个空闲套接字,它对内存提出了很高的要求。我不一定希望内核为您的发送缓冲区“固定”物理页面;我希望它会将该内存用于有用的东西。然后当你尝试发送时,内核需要为发送缓冲区获取一个空闲页面;如果没有这样的页面可用,它可能会决定返回EWOULDBLOCK
而不是等待(比如说)交换。
现在,有很多“可能”和“可能”,更熟悉内核内部的人可能会告诉我我错了。但即使 Linux 今天不这样,明天也可能会这样。您是否 100% 确定您永远不会在 Linux 以外的任何平台上运行您的应用程序?
所以我不会用如此脆弱的假设来编写我的应用程序。我建议您决定是否阻塞或非阻塞语义对您自己的代码更有意义,并且不要试图玩弄内核的内部结构。
[更新]
我希望我不必深入研究 Linux 内部,但过度自信的反对者驱使我这样做。
从“new_segment”标签处的 net/ipv4/tcp.c开始:
new_segment:
if (!sk_stream_memory_free(sk))
goto wait_for_sndbuf;
skb = sk_stream_alloc_skb(sk, 0, sk->sk_allocation);
if (!skb)
goto wait_for_memory;
看看“wait_for_sndbuf”与“wait_for_memory”有何不同?这就是我所说的。
在“wait_for_memory”标签处,有一个调用,sk_stream_wait_memory
其timeo
值取决于这是否是非阻塞发送。反过来,该函数要么使进程进入睡眠状态,要么EAGAIN
根据timeo
.
[更新 2]
只是为了清楚我在回答什么问题......
我将这个问题解释为,“如果我知道我的套接字的发送缓冲区有足够的可用空间,那么该套接字上的阻塞和非阻塞之间是否有任何区别——性能或其他方面send
?”
例如,如果您的协议是发送一条消息,然后仅在收到对前一条消息的回复后才发送一条新消息,则前提当然是可能的。在这种情况下,您知道发送缓冲区始终是空的send
。通过获取和/或设置 POSIX 标准SO_SNDBUF
套接字选项,您可以知道您的套接字有足够的可用空间。在这种情况下,阻塞的send
行为是否与非阻塞不同send
?
我对 POSIX 规范的阅读原则上说“是”。我对 Linux 源代码的阅读在实践中说“是”。我当然可能是错的,但需要对 POSIX 或 Linux 更了解的人来演示它,到目前为止,他们都没有回答这个问题。
[最终(?)更新]
这是我认为 POSIX 允许/要求您假设的内容。
如果发送缓冲区中有足够的可用空间,则阻塞send
不能永远阻塞。calloc
这与具有足够空闲虚拟内存的调用不能永远阻塞的含义相同。最终,系统将找到发送数据所需的资源。
(请注意,当发送缓冲区已满时,情况并非如此,在这种情况下,阻塞send
可能会永远阻塞,具体取决于套接字接收端发生的情况。)
但是,即使发送缓冲区中有足够的空间,非阻塞send
可能仍会返回EWOULDBLOCK
。因此,如果您使用非阻塞套接字,则无论您对发送缓冲区或其他任何内容了解多少,您的代码都必须处理这个问题。
请记住,您不能始终确保您的发送将被快速执行。
例如,如果另一侧的套接字没有使用 recv 读取,那么您的缓冲区将已满。
当然,如果您编写应用程序的两面并始终读取,我猜在性能方面不会有显着差异。
您可以“确保套接字发送缓冲区中始终有空间”的唯一方法是在它已满时不发送。
您确实可以确定发送缓冲区中是否有空间 - 在某些系统上。如果在阻塞模式下没有空间,您可以 select() 用于可写性 - 在某些系统上。在其他系统上,您无法判断是否有空间和/或您无法在阻塞模式下 select()。
在这样的系统上,除了发送和阻塞之外,您没有任何好的实现选择,或者使用阻塞模式。
在能知道但不能select()的系统上,可以循环和休眠,但是不知道要休眠多久,所以会休眠太久,浪费时间,不像阻塞,会阻塞合适的时间长度。