有人可以很好地解释非阻塞套接字的“发送”行为吗?

Posted

技术标签:

【中文标题】有人可以很好地解释非阻塞套接字的“发送”行为吗?【英文标题】:Can someone give me a good explanation of 'send' behavior for non-blocking sockets? 【发布时间】:2011-07-19 07:00:03 【问题描述】:

我现在至少阅读了 10 次文档,并且还阅读了大约 10 个代码 sn-ps 和使用非阻塞套接字发送数据的完整程序。问题是有些教程要么是为初学者准备的(Beejs f.i.),要么是他们的假设相当草率;那些并不复杂的是专门的代码示例,没有解释他们为什么要做他们所做的事情。在我看来,即使是 SO 知识库也不能详尽地涵盖 send 行为的整个范围。我所追求的是 f.e 的详细信息:

返回码 0 的确切含义是什么,是否值得检查 errno 还是应该直接丢弃连接而不进行进一步调查? 获得负返回值是否保证关闭连接变坏,还是只有这样,除非errnoEWOULDBLOCKEAGAINEINTR(...其他)? 当返回值为> 0 时是否值得检查errno?显然,该值表示“发送”的数据量(在引号中,因为它确实是一个很长的过程,对),但由于套接字是非阻塞的,这是否意味着可以立即发出另一个调用,或者,取决于@987654329 @ 再次,应该等待下一个发送时机(使用 select/poll/epoll)? 基本上,是否首先检查返回值,然后才检查errno 值?或者也许send 在每次调用时设置errno,不管返回值?这将使错误检查更容易一些... 如果一个人得到EINTR,对于程序来说,什么是好的、健壮的行为?只需记录状态并在下一次发送时重试,例如 EWOULDBLOCKEAGAIN? 是否同时检查 both EWOULDBLOCK EAGAIN?我们可以相信两者具有相同的价值,还是取决于实施? send 是否为流套接字返回 EMSGSIZE?如果不是,那么缓冲区大小不会太大,对吧? 返回值本身是否可以等于任一已知错误代码?

如果您能提供一个健壮的非阻塞发送代码示例,我们将不胜感激。

【问题讨论】:

【参考方案1】:

关于 EINTR 和系统调用:

如果您使用的是 GLIBC,则无需担心这一点,至少在系统调用的上下文中是这样。我是从Glibc FAQ 那里得到的,用 grep 表示“为什么不再发出信号中断系统调用?”

如果您使用的是 LINUX,那么您可能不必担心 connect() 系统调用的奇怪语义,这是 David Madore 对here 的抱怨。否则,请为异步 connect() 调用的异常行为做好准备。

【讨论】:

【参考方案2】:

这里有很多问题:

返回码 0 的确切含义是什么,是否值得检查 errno 还是应该直接丢弃连接而不进行进一步调查?

在 POSIX 系统上,send(2) 永远不会返回 0,除非您使用长度 arg 为 0 调用它。检查特定系统的文档以确保它遵循 POSIX 规范

获得负返回值是否保证关闭连接变坏,还是只有这样,除非 errno 是 EWOULDBLOCK、EAGAIN 或 EINTR(...其他)?

不,-1 返回值(唯一可能的负返回值)仅表示没有发送数据。您需要检查 errno 以了解原因 - 请参阅 send(2) 手册页以获取所有可能的 errno 值及其含义的完整列表

当返回值 > 0 时是否值得检查 errno?显然,该值表示“发送”的数据量(在引号中,因为它确实是一个很长的过程,对),但是由于套接字是非阻塞的,这是否意味着可以立即发出另一个调用,或者,再次取决于 errno , 是否应该等待下一个发送时机(使用 select/poll/epoll)?

如果 send 返回成功 (> 0),则 errno 将保持不变,并将包含之前的任何内容(这可能是早期系统调用的错误)。

基本上,是否首先检查返回值,然后才检查 errno 值?或者也许在每次调用时发送设置 errno,不管返回值?这将使错误检查更容易一些...

先检查返回值,如果返回值为-1,再检查errno。如果你真的想,你可以在调用之前将errno设置为0,然后再检查它

如果一个人获得了 EINTR,那么对于一个程序来说,什么是一个好的、健壮的行为呢?只需记录状态并在下次发送时重试,例如使用 EWOULDBLOCK 和 EAGAIN?

嗯,最简单的方法是禁用系统调用的中断,在这种情况下你永远不会得到 EINTR。像 EWOULDBLOCK/EAGAIN 一样对待它也很好。

是否同时检查 EWOULDBLOCK 和 EAGAIN?我们可以相信两者具有相同的价值,还是取决于实施?

取决于实现,但通常它们是相同的。有时 SysV 与 BSD 仿真模式会出现一些奇怪的情况,这可能会使它们有所不同,并且可能会发生任何一种情况

发送流套接字是否返回 EMSGSIZE?如果不是,那么缓冲区大小不会太大,对吧?

流套接字没有原子消息,EMSGSIZE 仅用于原子消息,所以不,流套接字不能返回 EMSGSIZE

返回值本身是否可以等于任一已知错误代码?

唯一的错误代码是-1。成功是写入的字节数,所以如果你可以在 32 位机器上写入 2^32-1 字节(或在 64 位机器上写入 2^64-1),那将是一个问题,但你不能写那么多字节(如果你尝试,你通常会得到 EINVAL 或 EFAULT)。

【讨论】:

send(2) 如果为 len 传递了零,则可以返回 0。对于像 UDP 这样的数据报协议,这甚至会导致发送一个零字节的数据包。此外,不能保证 errno 在成功的库调用中保持不变。 @Anomie:第一部分是正确的,但对于第二部分,POSIX 确实保证某些库调用在没有错误的情况下不会修改 errno,send 就是其中一个调用。 你在哪里看到的? POSIX.1-2008 在线here。 The page on errno 表示“未指定成功调用函数后的 errno 设置,除非该函数的描述指定不应修改 errno”,而 the page for send 似乎没有说任何这样的事情。 @Anomie -- 有趣的是,我指的是我拥有的 POSIX.1 的旧印刷副本(日期为 1992 年)。显然,如果系统调用没有错误,他们已经取消了不修改 errno 的要求。【参考方案3】:

我会尽力回答你的问题。

send 的返回值 0 表示发送了 0 个字节。错误由返回值 -1 指示。如果您以 0 的长度调用 send,则预期返回 0。虽然非阻塞套接字应该返回 -1 并带有 EAGAIN 或 EWOULDBLOCK 的 errno(如果它会阻塞),但如果某些实现返回写入的 0 字节,我不会过于惊讶。 EWOULDBLOCK、EAGAIN 和 EINTR 是您应该重试的错误,收到其中之一时不要关闭连接。其他错误确实表明可能会导致关闭的问题。 不,在库调用成功后不要检查 errno(除非文档明确说明您可以出于某种原因执行此操作;我不知道有任何临时操作者这样做)。请注意,errno 在成功的库调用中可能不会保持不变,因为该调用可能进行了其他调用,这些调用返回了预期并正确处理的错误(例如,调用可能会尝试统计一个文件,完全期望它可能不存在;errno即使没有真正的错误,也会是 ENOENT)。如果 send 返回一个简短的写入,您可以再试一次(可能会得到 EWOULDBLOCK/EAGAIN),或者您可以等待下一个 select。 是的,先检查返回值。如果调用成功,errno 不会告诉你任何有用的信息。 在 EINTR 上,您可以立即重试,也可以通过 select 循环等待下一次。 您必须同时检查 EAGAIN 和 EWOULDBLOCK;如果性能特别重要,我想您可以使用#if EAGAIN == EWOULDBLOCK(但请记住,先分析然后优化)。 这一切都取决于底层协议,但通常我希望流协议没有原子消息(除非可能在使用 MSG_OOB 时)。对于 TCP,任何缓冲区大小都可以。 返回值当然可以等于任何 errno 常量,但这没有任何意义。例如,在我的系统上,如果写入 11 个字节,则返回值将等于 EAGAIN。

HTH。

【讨论】:

以上是关于有人可以很好地解释非阻塞套接字的“发送”行为吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用非阻塞套接字使用“sendmsg/recvmsg”将 FD 从一个进程发送到另一个进程吗?

名词解释:同步、异步、阻塞和非阻塞

非阻塞套接字轮询与阻塞套接字

Socket编程中,阻塞与非阻塞的区别

POSIX - 理解非阻塞发送实现

非阻塞套接字仍然可以在 OpenSSL 中阻塞吗?