当非阻塞 send() 只传输部分数据时,我们可以假设它会在下一次调用时返回 EWOULDBLOCK 吗?

Posted

技术标签:

【中文标题】当非阻塞 send() 只传输部分数据时,我们可以假设它会在下一次调用时返回 EWOULDBLOCK 吗?【英文标题】:When a non-blocking send() only transfers partial data, can we assume it would return EWOULDBLOCK the next call? 【发布时间】:2013-10-23 20:19:29 【问题描述】:

非阻塞套接字的手册页中有两个案例有详细记录:

如果 send() 返回与传输缓冲区相同的长度,则整个传输成功完成,并且套接字可能处于也可能不处于返回 EAGAIN/EWOULDBLOCK 的状态下一次调用 > 0 个字节要传输。 如果 send() 返回 -1 且 errno 为 EAGAIN/EWOULDBLOCK,没有任何传输完成,程序需要等待套接字准备好接收更多数据(epoll 情况下为 EPOLLOUT )。

非阻塞套接字没有记录的是:

如果 send() 返回小于缓冲区大小的正值。

假设 send() 会在多一个字节的数据上返回 EAGAIN/EWOULDBLOCK 是否安全?或者非阻塞程序是否应该再尝试一次 send() 以获得最终的 EAGAIN/EWOULDBLOCK?如果 EPOLLOUT 观察程序实际上并未处于“将阻止”状态以响应它的输出,我担心会在套接字上放置一个 EPOLLOUT 观察程序。

显然,后一种策略(再次尝试得出结论)具有明确定义的行为,但它更冗长并且会影响性​​能。

【问题讨论】:

@Damon 您的编辑完全改变了问题的含义。 @EJP:OP 显然知道EWOULDBLOCK (或者在大多数情况下,非阻塞套接字通常如何工作),所以在我看来,“会阻塞”的措辞是一个安全的赌注" 这似乎使您感到困惑,这只是一个糟糕的措辞,但不是本意。 @Damon 这对我来说一点也不明显。显然,这正是让 OP 感到困惑的原因。不是我。这就是全部和完整的观点。通过从问题中删除它,您删除了它的全部含义。不要那样做。如果您想回答这个问题,请务必这样做。但不要只是改变它以适合自己。 达蒙是正确的。我已将问题更新为更准确。我知道非阻塞套接字实际上永远不会阻塞,只是返回它们会阻塞。 你不能假设任何事情。网卡驱动程序可能是异步的,您的计算机可能是异步的,等等...发送缓冲区可能在您的发送过程中被另一个内核耗尽等等... 【参考方案1】:

如果 send() 返回与传输缓冲区相同的长度,则整个传输成功完成,并且套接字可能处于阻塞状态,也可能不处于阻塞状态。

没有。套接字保持原来的模式:在这种情况下,非阻塞模式,在下面始终假设。

如果 send() 返回 -1 并且 errno 是 EAGAIN/EWOULDBLOCK,则任何传输都没有完成,程序需要等待,直到套接字不再阻塞。

直到发送缓冲区不再满。套接字保持非阻塞模式。

如果 send() 返回一个小于缓冲区大小的正值。

套接字发送缓冲区中只有那么多空间。

假设 send() 会阻塞多一个字节的数据是否安全?

“假设 [它] 会阻塞”根本不“安全”。它不会。它处于非阻塞模式。 EWOULDBLOCK 表示它在阻塞模式下阻塞

或者非阻塞程序是否应该再尝试一次 send() 以获得最终的 EAGAIN/EWOULDBLOCK?

这取决于你。 API 的工作方式由您决定。

如果 EPOLLOUT 观察程序实际上没有阻塞,我担心它会在套接字上放置。

这不是“阻止”。它不会阻止任何事情。它处于非阻塞模式。发送缓冲区在那一刻被填满。片刻之后它可能完全是空的。

我不明白你在担心什么。如果您有待处理的数据并且最后一次写入并没有全部发送,请选择可写性,并在获得时写入。如果这样的 write 发送所有内容,下次不要选择可写。

套接字通常是可写的,除非它们的发送缓冲区已满,所以不要一直选择可写,因为你只是得到一个自旋循环。

【讨论】:

【参考方案2】:

send 的调用具有三种可能的结果:

    发送缓冲区中至少有一个字节可用 →send 成功并返回接受的字节数(可能比您要求的少)。 在您调用 send 时,发送缓冲区已满。 →如果套接字阻塞,send 阻塞 →如果套接字是非阻塞的,send 失败,EWOULDBLOCK/EAGAIN 发生错误(例如用户拔掉网线,对等方重置连接)→send 失败并出现另一个错误

如果send 接受的字节数小于您要求的数量,那么这意味着发送缓冲区现在已满。但是,对于将来对send 的任何调用,这纯粹是间接的,非权威性的。send 返回的信息只是您调用send 时当前状态的“快照”。当send 已返回或您再次致电send 时,此信息可能已经过时。当你的程序在send 中时,网卡可能会在网络上放置一个数据报,或者一纳秒后,或者在任何其他时间——没有办法知道。您会知道下一次调用何时成功(或何时失败)。

换句话说,这暗示下一次对send 的调用将返回EWOULDBLOCK/EAGAIN(或者如果套接字不是非阻塞的,则会阻塞)。尝试直到你所谓的“得到一个结论性的EWOULDBLOCK”是正确的做法。

【讨论】:

我认为这仅适用于非阻塞:“发送成功并返回接受的字节数(可能比您要求的少)。”阻塞套接字应该阻塞,直到它完成发送所有数据或由于其他原因失败。我要么将阻塞场景添加到#1,要么从#2中删除阻塞场景(仅针对非阻塞套接字做出答案)。 @DavidTimothyStrauss:令人惊讶的是,您的假设完全符合联机帮助页和 POSIX 的措辞(类似的 write 系统调用中的措辞明确提到了部分写入)。虽然这种措辞对于数据报套接字(您不能send! 一起使用)有意义,但对于面向连接的套接字却没有意义。发送例如一次 100kB 当然是允许的,但是给定 64kB 的“典型”缓冲区大小,如果整个消息必须适合,那将意味着您的程序将阻塞 forever(因为它永远不适合完整的消息进入缓冲区)。 对于数据报套接字,当然,这是有道理的,因为只能发送完整的消息,完整的消息必须适合缓冲区。但是数据报套接字需要有一个缓冲区,可以容纳至少一个最大大小的数据报。这意味着sendto 不会永远阻塞。对于流套接字,部分发送已成为现实 3 年(不管手册页中的令人困惑的措辞如何),例如参见 Beej's guide 关于该主题(Beej 的网络指南通常是一本很好的读物)。 几年前在 news:comp.protocols.tcp-IP 上有关于这一点的讨论,普遍认为阻塞 send()s 会阻塞,直到所有数据都被缓冲。该新闻组由 TCP 的所有实现者组成。他们应该知道。 @Damon Blocking send() 保证数据已至少被缓冲。它可能会经历多个缓冲-发送-缓冲循环,并在整个 64MiB 被完全发送或缓冲后返回。

以上是关于当非阻塞 send() 只传输部分数据时,我们可以假设它会在下一次调用时返回 EWOULDBLOCK 吗?的主要内容,如果未能解决你的问题,请参考以下文章

关系 和非关系 阻塞非阻塞的区别

socket缓冲区以及阻塞模式

socket缓冲区以及阻塞模式

网络编程Socket的阻塞和非阻塞IO

[转]socket使用TCP协议时,sendrecv函数解析以及TCP连接关闭的问题

套接字发送与 sendasync。啥时候发送实际阻塞?