阻塞套接字:确切地说,“send()”何时返回?

时间:2011-03-23 14:58:16

标签: tcp udp sockets

确切地说,BSD套接字send()函数何时返回给调用者?

非阻止模式中,它应该立即返回,正确吗?

至于阻止模式,man page说:

  

当消息不适合套接字的发送缓冲区时, send()通常会阻塞,除非套接字已置于非阻塞I / O模式。

问题:

  1. 这是否意味着如果内核发送缓冲区中有空间,send()调用将始终立即返回?
  2. send()调用的行为和性能是否与TCP和UDP相同?如果没有,为什么不呢?

5 个答案:

答案 0 :(得分:32)

  

这是否意味着如果内核发送缓冲区中有空间,send()调用将始终立即返回?

是。只要在您提供的内存之后立即意味着它已被复制到内核的缓冲区。在某些边缘情况下,这可能不是那么直接。例如,如果传入的指针触发了一个页面错误,需要从内存映射文件或交换中提取缓冲区,这会给返回的调用带来显着的延迟。

  

对于TCP和UDP,send()调用的行为和性能是否相同?如果没有,为什么不呢?

不完全。可能的性能差异取决于OS对TCP / IP堆栈的实现。从理论上讲,UDP套接字可能会稍微便宜一些,因为操作系统需要用它做更少的事情。

编辑:另一方面,由于您可以使用TCP为每个系统调用发送更多数据,因此TCP的每字节成本通常会低很多。在最近的Linux内核中,sendmmsg()可以减轻这种情况。

至于行为,它几乎相同。

对于阻塞套接字,TCP和UDP都将阻塞,直到内核缓冲区中有空间。然而,区别在于UDP套接字将等待,直到整个缓冲区可以存储在内核缓冲区中,而TCP套接字可能决定只将一个字节复制到内核缓冲区中(通常它不止一个字节)。

如果您尝试发送大于64kiB的数据包,UDP套接字可能会始终以 EMSGSIZE 失败。这是因为作为数据报套接字的UDP保证将整个缓冲区作为单个IP数据包(或一组IP数据包片段)发送,或者根本不发送它。

非阻塞套接字的行为与阻塞版本完全相同,但有一个例外,即不是阻塞(如果内核缓冲区中没有足够的空间),则调用将失败并显示 EAGAIN (或 EWOULDBLOCK )。当发生这种情况时,是时候将套接字放回epoll / kqueue / select(或者你正在使用的任何东西)等待它再次变为可写。

正常情况下处理POSIX时,请记住,您的呼叫可能会因 EINTR 而失败(如果呼叫被信号中断)。在这种情况下,您很可能想再次致电send()

答案 1 :(得分:5)

如果内核缓冲区中有空间,则send()将尽可能多的字节复制到缓冲区并立即退出,返回实际复制的字节数(可能少于您请求的字节数) 。如果内核缓冲区中没有空间,则send()将阻塞,直到任一个房间可用或发生超时(如果配置了一个)。

答案 2 :(得分:1)

一旦内核接受了数据,send()就会返回。 在阻塞套接字的情况下:如果内核缓冲区不足以接收提供给send()调用的数据,则send()将阻塞。

非阻塞套接字:send()不会阻塞,但会失败并返回-1,或者它可能会返回部分复制的字节数(取决于可用的缓冲区空间)。它设置了errno EWOULDBLOCK或EAGAIN。这意味着在send()的那个时候,缓冲区无法吸收所有字节,你应该再次使用select()调用再次发送()数据。或者你可以放一个带sleep()的循环并调用send(),但你必须处理实际发送的字节数和要发送的剩余字节数。

答案 3 :(得分:0)

  

这是否意味着send()调用   如果,将永远返回   内核中有空间发送   缓冲

不应该吗?可以不同地定义数据“被发送”的时刻。我认为这是OS接受您的数据在堆栈上交付的时刻。否则,定义它是非常困难的。是时候,数据传输到网卡缓冲区了吗?或者在数据被推出网卡缓冲区之后?

您是否有任何问题需要确定,或者您只是好奇?

答案 4 :(得分:0)

你的推定是正确的。如果内核发送缓冲区中有空间,内核会将数据复制到发送缓冲区,send()将返回。