【问题标题】: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?如果不是,那么缓冲区大小不会太大,对吧?
  • 返回值本身是否可以等于任一已知错误代码?

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

【问题讨论】:

    标签: sockets send nonblocking berkeley-sockets


    【解决方案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 在线hereThe 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。

      【讨论】:

        猜你喜欢
        • 2015-11-27
        • 2011-08-19
        • 1970-01-01
        • 1970-01-01
        • 2013-06-04
        • 1970-01-01
        • 2020-02-19
        • 2010-10-31
        • 2013-10-15
        相关资源
        最近更新 更多