阻塞发送和非阻塞发送有什么区别?

8
如果应用程序能确保套接字的发送缓冲区始终有空间,那么阻塞和非阻塞发送是否具有相同的性能?在这种情况下,两种方法是否有任何优势?

5
为什么不两者都实施,并进行一些测试呢? - xiaoyi
1
为什么你想这么做?系统已经为你提供了一种完成工作的方式。 - user703016
2
@xiaoyi - 内核中有许多、许多代码路径,一个简单的测试程序可能无法覆盖到所有情况。这是一个合理的问题,“试一试”并不是一个有用的回答。 - Nemo
@Nemo但是如果这很复杂,怎么回答这个问题? - MK.
1
@xiaoyi 这个问题是关于“优点”,而不是性能。还有其他种类的优点。 - user207421
3个回答

5
阻塞和非阻塞send之间的唯一区别是内核是否将进程置于休眠状态或返回EWOULDBLOCK。因此,就性能而言,不应该有任何区别。
然而,我怀疑你的隐含假设是发送操作不能被阻塞,因为发送缓冲区有空闲空间。想象一下,在对内存要求很高的系统上空闲的套接字,我不一定希望内核“钉住”你的发送缓冲区的物理页面;我希望它用那些空间来做些有用的事情。然后当你尝试发送时,内核将需要为发送缓冲区获取一个空闲页面;如果没有这样的页面可用,它可能会决定返回EWOULDBLOCK而不是等待(比如)交换操作。
现在,这有很多“可能”和“可能性”,更熟悉内核内部的人可能会告诉我我错了。但即使Linux今天不这样运行,明天也可能会这样;你能100%确定你永远不会在除Linux以外的任何东西上运行你的应用程序吗?
所以我不会用如此脆弱的假设来编写我的应用程序。我建议您决定阻塞或非阻塞语义对于您自己的代码更有意义,并且不要试图操纵内核的内部机制。
[更新]
我希望我不必深入了解Linux内部,但一个过于自信的投票者让我不得不这样做。
net/ipv4/tcp.c在“new_segment”标签处开始:
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值。该函数又会根据timeo将进程置于睡眠状态或返回EAGAIN
[更新2]
只是为了明确我回答的问题...
我理解这个问题是,“如果我知道我的套接字的发送缓冲区有足够的空闲空间,在该套接字上进行阻塞和非阻塞的send之间是否有任何差异-性能或其他方面?”
假设您的协议是发送一条消息,然后只有在接收到上一条消息的回复后才发送新消息,那么这种前提是完全可能的。在这种情况下,您知道当您进行“发送”操作时,发送缓冲区始终为空。通过获取和/或设置符合POSIX标准的SO_SNDBUF套接字选项,您可以知道套接字具有足够的可用空间。在这种情况下,阻塞式send与非阻塞式send是否会表现不同?
根据我的POSIX规范阅读,原则上是“是”。根据我对Linux源代码的阅读,实际上也是“是”。我当然可能是错的,但需要更了解POSIX或Linux的人来证明它,并且迄今为止没有人回答这个问题。
[最终(?)更新]
以下是我认为POSIX允许/要求您做出的假设。
如果发送缓冲区中有足够的可用空间,则阻塞式send不能永远阻塞。这与使用足够的可用虚拟内存调用calloc的方式相同,不能永远阻塞。最终,系统将找到发送数据所需的资源。
(当发送缓冲区已满时,情况并非如此。在这种情况下,阻塞式的send可能会永远阻塞,具体取决于套接字接收端正在发生什么。)然而,即使发送缓冲区有足够的空间,非阻塞式send仍然可能返回EWOULDBLOCK。因此,如果您使用非阻塞式套接字,则您的代码必须处理这个问题,无论您对发送缓冲区或其他任何事情了解多少。

你对发送缓冲区进行了猜测。它在创建套接字时永久分配,并且根据TCP的语义需要在send()调用之间保持持久性。这就是它的作用所在。DV - user207421
@EJP - 好的,我停止了猜测并添加了内核源代码的引用。请删除你的负票或者证明这段代码路径是不可能的。 - Nemo
Nemo,当然,你的更新2反映了我的用例,即每次只有一个未确认的消息。消息大小在1K-3KB之间。因此,分配了4KB的SO_SNDBUF。在可用足够内存的情况下,是否还有其他区别? - Jimm
1
@Jimm,我已经再次更新了我的答案。请注意,正确的问题不是“Linux今天做什么?”,而是“如果我想让我的代码今天和明天都能运行,我应该假设什么?” - Nemo

-1

请记住,您不能总是确定您的发送将被快速执行。

例如,如果另一端的套接字未使用recv进行读取,则您的缓冲区将会满。

当然,如果您编写应用程序的两个方面并始终进行读取,则在性能方面不会有显着差异。


您忽略了问题的一部分,具体来说,是从“如果应用程序可以确保…”开始的那部分。前五个单词。 - user207421
“应用程序”也负责在套接字上读取数据吗?这并没有说明。 - Intrepidd
第一句话假定应用程序能确保发送缓冲区有足够的空间。你提出的问题与主题无关。 - user207421

-1

确保套接字发送缓冲区始终有空间的唯一方法是在缓冲区已满时不进行发送。

您确实可以确定发送缓冲区是否有空间 - 在某些系统上。如果在阻塞模式下没有空间,则可以选择可写性 - 在某些系统上。在其他系统上,您无法确定是否有空间和/或无法在阻塞模式下进行选择(select())。

在这种系统上,您没有任何好的实现选择,除了发送并阻塞,或者使用阻塞模式。

在您可以知道但无法选择(select())的系统上,您可以循环和休眠,但您无法知道要休眠多长时间,因此您将会浪费时间睡得太久,与阻塞相比,阻塞会准确地阻塞正确长度的时间。


问题是,“阻塞和非阻塞发送之间有什么区别?”(当已知发送缓冲区中有空间时)。我理解你的回答是“没有”,这是错误的。你的意思是要回答“是”吗? - Nemo
@Nemo,你怎么能把所有这些解释成一个简单的“不行”对我来说真是个谜。我在回答他的问题前提以及它们在阻塞模式下无效的原因。我没有提到非阻塞模式。这太荒谬了。 - user207421
@Nemo,你对问题的荒谬误解本身就值得被踩。他问的区别在于其中一部分并不存在于任何有用的形式中,而这就是我的答案。真的很简单。 - user207421
你对此的看法也是错误的。在所有系统上,确实有可能知道你的发送缓冲区是否有空间。例如,你的代码可能只在收到前一个消息的回复后才发送TCP消息。在这种情况下,你知道发送缓冲区始终为空;通过设置SO_SNDBUF套接字选项,你可以知道它有多少可用空间。即使在这种情况下,当系统处于内存压力下时,阻塞和非阻塞套接字的行为也可能不同。 - Nemo
@Nemo,我不知道OP系统的行为,你也不知道。显然,我指的是系统调用的可用性或否告诉您发送缓冲区的当前状态。当你把话放在OP嘴里和我的嘴里时,当你完成对我所写内容的故意曲解,并面对我没有写下你所说的那样的事实,这就是你提出的你的反对理由,而我根本没有在这里提供一个是/否的答案,我们可能会有所进展。 - user207421
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接