当 BSD 套接字的 send()
函数返回给调用者时,它到底是在什么时候返回的呢?
在非阻塞模式下,它应该立即返回,对吗?
对于 阻塞模式,手册中说:
当消息不适合套接字的发送缓冲区时,send() 通常会阻塞,除非将套接字置于非阻塞 I/O 模式。
问题:
- 这是否意味着如果内核发送缓冲区有空间,那么
send()
调用将总是立即返回? send()
在 TCP 和 UDP 上的行为和性能是否相同?如果不同,原因是什么?
这是否意味着,如果内核发送缓冲区中有空间,send()调用将总是立即返回?
是的。只要“立即”指的是在你提供的内存被复制到内核缓冲区后就会立即返回。然而,在某些边缘情况下,可能并不那么立即。例如,如果您传递的指针触发需要从存储在内存映射文件或交换空间中的缓冲区中拉取数据的页故障,则会为调用返回添加显著延迟。
对于TCP和UDP,send()调用的行为和性能是否相同?如果不是,为什么?
不完全相同。可能的性能差异取决于操作系统实现TCP/IP协议栈的方式。理论上,UDP套接字可能会稍微更便宜,因为操作系统需要处理的工作较少。
编辑:另一方面,由于您可以使用TCP每次系统调用发送更多的数据,因此每个字节的成本通常可以更低。这可以通过最近Linux内核中的sendmmsg()来缓解。
至于行为,它几乎相同。
对于阻塞套接字,TCP和UDP都将阻塞,直到内核缓冲区中有空间。但是,区别在于UDP套接字将等待,直到您的整个缓冲区可以存储在内核缓冲区中,而TCP套接字可能只会将单个字节复制到内核缓冲区中(通常是多个字节)。
如果您尝试发送大于64kiB的数据包,则UDP套接字很可能会始终失败,并显示EMSGSIZE。这是因为作为数据报套接字的UDP保证将您的整个缓冲区作为单个IP数据包(或IP数据包片段列车)发送,否则不发送。
非阻塞套接字与阻塞版本完全相同,唯一的区别是当内核缓冲区中没有足够空间时,调用不会被阻塞,而是以 EAGAIN (或 EWOULDBLOCK) 失败。当出现这种情况时,需要将套接字重新放回 epoll/kqueue/select(或者使用的任何其他机制)中等待它再次变得可写。
像 POSIX 的常规操作一样,要记住您的调用可能会因为信号中断而失败,并返回EINTR。在这种情况下,最好重新调用 send()
。
send()
会立即失败,并显示错误ENOBUFS
。无论套接字是阻塞还是非阻塞,都会发生这种情况。因此,在这些系统上,UDP套接字永远不会在发送时阻塞,也永远不会返回EAGAIN
。这是合法的行为,因为UDP本身就不保证数据传递。如果发送缓冲区已满,则会出现积压,沿途总会有一些积压会被丢弃,最好尽早发现以便应用程序能够及时做出反应。 - Meckisend()
会尽可能多地将字节复制到缓冲区中,并立即退出,返回实际复制的字节数(可能少于请求的字节数)。如果内核缓冲区没有空间,则send()
会阻塞,直到有空间可用或超时发生(如果已配置)。send()
将会阻塞(如果处于阻塞模式),直到有空间可用,或者立即失败并返回EWOULDBLOCK
或EAGAIN
(如果处于非阻塞模式)。如有需要,您可以使用select()
,poll()
等方法在调用send()
之前检测是否有空间可用。 - Remy Lebeau你的假设是正确的。如果内核发送缓冲区有空间,内核将把数据复制到发送缓冲区中,send()
函数将返回。