阻塞套接字:确切地说,“send()”何时执行?返回?
BSD 套接字 send()
函数何时返回调用者?
在非阻塞模式下,它应该立即返回,对吗?
至于阻塞模式,手册页 说:
当消息无法放入套接字的发送缓冲区时,send() 通常会阻塞,除非套接字已置于非阻塞 I/O 模式。
问题:
- 这是否意味着如果内核发送缓冲区中有空间,
send()
调用将始终立即返回? - 对于 TCP 和 UDP,
send()
调用的行为和性能是否相同?如果没有,为什么不呢?
When, exactly, does the BSD socket send()
function return to the caller?
In non-blocking mode, it should return immediately, correct?
As for blocking mode, the man page says:
When the message does not fit into the send buffer of the socket, send() normally blocks, unless the socket has been placed in non-blocking I/O mode.
Questions:
- Does this mean that the
send()
call will always return immediately if there is room in the kernel send buffer? - Is the behavior and performance of the
send()
call identical for TCP and UDP? If not, why not?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
是的。只要立即意味着您提供的内存已被复制到内核的缓冲区中。在某些边缘情况下,这可能不会那么直接。例如,如果您传入的指针触发页面错误,需要从内存映射文件或交换中拉入缓冲区,这将显着增加调用返回的延迟。
不完全是。可能的性能差异取决于操作系统对 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()
。Yes. As long as immediately means after the memory you provided it has been copied to the kernel's buffer. Which, in some edge cases, may not be so immediate. For instance if the pointer you pass in triggers a page fault that needs to pull the buffer in from either a memory mapped file or the swap, that would add significant delay to the call returning.
Not quite. Possible performance differences depends on the OS' implementation of the TCP/IP stack. In theory the UDP socket could be slightly cheaper, since the OS needs to do fewer things with it.
EDIT: On the other hand, since you can send much more data per system call with TCP, typically the cost per byte can be a lot lower with TCP. This can be mitigated with sendmmsg() in recent linux kernels.
As for the behavior, it's nearly identical.
For blocking sockets, both TCP and UDP will block until there's space in the kernel buffer. The distinction however is that the UDP socket will wait until your entire buffer can be stored in the kernel buffer, whereas the TCP socket may decide to only copy a single byte into the kernel buffer (typically it's more than one byte though).
If you try to send packets that are larger than 64kiB, a UDP socket will likely consistently fail with EMSGSIZE. This is because UDP, being a datagram socket, guarantees to send your entire buffer as a single IP packet (or train of IP packet fragments) or not send it at all.
Non blocking sockets behave identical to the blocking versions with the single exception that instead of blocking (in case there's not enough space in the kernel buffer), the calls fail with EAGAIN (or EWOULDBLOCK). When this happens, it's time to put the socket back into epoll/kqueue/select (or whatever you're using) to wait for it to become writable again.
As usual when working on POSIX, keep in mind that your call may fail with EINTR (if the call was interrupted by a signal). In this case you most likely want to call
send()
again.如果内核缓冲区中有空间,则
send()
将尽可能多的字节复制到缓冲区中并立即退出,返回实际复制的字节数(可能比您设置的字节数少)要求)。如果内核缓冲区中没有空间,则send()
会阻塞,直到任一空间可用或发生超时(如果已配置)。If there is room in the kernel buffer, then
send()
copies as many bytes as it can into the buffer and exits immediately, returning how many bytes were actually copied (which can be fewer than how many you requested). If there is no room in the kernel buffer, thensend()
blocks until either room becomes available or a timeout occurs (if one is configured).一旦内核接受了数据,send()就会返回。
在阻塞套接字的情况下:如果内核缓冲区没有足够的空闲空间来接收提供给 send() 调用的数据,则 send() 将阻塞。
非阻塞套接字:send() 不会阻塞,但会失败并返回 -1,或者可能返回部分复制的字节数(取决于可用的缓冲区空间)。它设置 errno EWOULDBLOCK 或 EAGAIN。这意味着在 send() 时,缓冲区无法接收所有字节,您应该再次尝试使用 select() 调用来再次发送数据。或者您可以使用 sleep() 放置一个循环并调用 send(),但您必须注意实际发送的字节数和要发送的剩余字节数。
The send() will return as soon as the data has been accepted by the kernel.
In case of blocking socket: The send() will block if the kernel buffer is not free enough to intake the data provided to send() call.
Non blocking sockets: send() will not block, but would fail and returns -1 or it may return number of bytes copied partially(depending on the buffer space available). It sets the errno EWOULDBLOCK or EAGAIN. This means at that time of send(), the buffer was not able to intake all the bytes and you should try again with select() call to send() the data again. Or you could put a loop with a sleep() and call send(), but you have to take care of number of bytes actually sent and the remaining number of bytes that are to be sent.
不应该吗?数据“发送”之后的时刻可以有不同的定义。我认为这是操作系统接受您的数据并在堆栈上传递的时刻。否则很难定义它。是不是数据传输到网卡缓冲区的那一刻?或者在数据被推出网卡缓冲区的那一刻之后?
您是否有任何问题需要确定或只是好奇?
Shouldn't it? The moment after which the data "is sent" can be defined differently. I think this is a moment when OS accepted your data for delivery on stack. Otherwise it's quite diffucult to define it. Is it a moment, when data is transmitted to network card buffer? Or after the moment when data is pushed out of network card buffer?
Is there any problem you need to know this for sure or you are just curious?
你的推测是正确的。如果内核发送缓冲区有空间,内核会将数据复制到发送缓冲区,并且
send()
将返回。Your presumption is correct. If there is room in the kernel send buffer, the kernel will copy the data into the send buffer and
send()
will return.