POSIX - 理解非阻塞发送实现

Posted

技术标签:

【中文标题】POSIX - 理解非阻塞发送实现【英文标题】:POSIX - understanding the non blocking send implementation 【发布时间】:2018-12-17 05:01:03 【问题描述】:

我想更好地理解非阻塞send 到套接字的实现。

鉴于我使用非阻塞套接字,并使用字符缓冲区“buf”发送数据,send 返回 EAGAIN 错误。我可以假设“buf”内容被复制到 POSIX 框架/网络堆栈中的内部缓冲区,我可以使用“buf”将新数据发送到套接字或EAGAIN 表示“buf”中的数据稍后将被复制,因此不应该更改“buf”内存?

我对此进行了很多搜索,但没有找到明确的答案。

对于aio_write,我在手册页中找到了以下注释:

被写出的缓冲区不能被访问 在操作期间或可能出现未定义的结果。记忆 涉及的区域必须保持有效。

但对于非阻塞我没有找到类似的东西。

谢谢。

【问题讨论】:

EAGAIN 中的 E 代表错误。 EGAIN 是一个错误。这意味着发送任何东西都没有(甚至不是部分)成功。您应该使用相同的参数再次尝试。 【参考方案1】:

了解非阻塞 I/O 和异步 I/O 是不同的模型很重要。

使用非阻塞 I/O,每个 sendrecv 都会立即发生,但前提是它可以在不阻塞调用进程的情况下完成。这意味着,对于send,内核套接字缓冲区中至少有一些空间用于发送正在发送的一些数据。对于recv,至少要接收一些数据字节。在这些情况下,调用将“成功”,分别返回发送或接收的字节数。否则,调用将失败,返回-1 并将errno 设置为EAGAIN(并且不会向缓冲区发送或接收或复制任何内容)。

另一方面,aio_write(和aio_read)是真正异步的:I/O 操作与这些调用一起排队,即使无法立即取得进展也会继续进行。当您的进程正在执行其他操作时,内核将继续监视和“引导”请求。因此,对于aio_write,操作可以立即完成,也可以稍后完成。在任何一种情况下,您都必须轮询完成,或者使用sigevent 机制来获得完成通知——如aio(7)sigevent(7) 手册页中所述。

在执行aio_write 后修改缓冲区的注意事项是因为您已经将写入操作排入队列但内核不一定尚未消耗您的缓冲区,因此如果您要修改其内容,则不确定内核是否会消耗旧内容或新内容。

同样的警告(并且确实)适用于修改分配给普通send(无论是阻塞还是非阻塞)的缓冲区@987654336 @call 正在运行。但是,由于普通的 send 相对于发送线程是同步的,因此您可以做到这一点的唯一方法是在原始线程中执行 send 操作时从另一个线程修改它。

【讨论】:

【参考方案2】:

send(2) 的手册页指出:

EAGAIN 或 EWOULDBLOCK

套接字被标记为非阻塞并且请求的操作 会阻止。 POSIX.1-2001 允许返回任一错误 对于这种情况,并且不需要这些常数具有 相同的值,因此便携式应用程序应检查 两种可能性。

鉴于您使用的是非阻塞 I/O,因此解释方法是: 套接字另一端的接收器正忙,没有准备好接收数据。因此,您可以假设buf的内容没有被使用(也没有被复制到内核中)。

仅供参考:您可以通过在套接字的文件描述符上执行select(2) 来确定它何时准备好接收数据来防止此错误。

【讨论】:

对此的解释方式是本地发送缓冲区已满,这反过来意味着远程接收缓冲区已满,这反过来意味着接收器读取的速度比您发送的慢。跨度> 【参考方案3】:

... 返回 EAGAIN 错误。我可以假设“buf”内容被复制到内部缓冲区

不,错误意味着没有复制任何内容,您需要重试整个缓冲区。

请记住,您还应该检查非错误返回值,因为它可能会成功写入缓冲区的部分

我对此进行了很多搜索,但没有找到明确的答案

好的,我同意手册页对此不是很明确。相反,让我们看看接口,想想当我们调用它时可能会发生什么:

ssize_t result = send(sockfd, buf, len, 0);

if (result < 0) 
    /* we have an error, so examine errno */
    return;

if (result == len) 
    /* we sent the whole buffer and can reuse buf at will */

else 
    /* 0 <= result < len, so we sent some of buf, but not all.
     * It's not an error but we need to keep buf[result..len] for later
     */

这三个分支涵盖了所有可能性 - 它不能返回它们所涵盖的任何其他

它要么发送一些东西,要么什么都不发送,如果它发送一些东西,我们需要知道多少字节。

它要么成功(返回 >= 0)要么失败,如果失败,我们也无法知道发送了多少字节。

因此,编写此接口的唯一合理方法是在出现错误时不发送任何字节 - 否则我们将处于不确定状态(我们无法确定是否需要一些、不需要或全部 buf重试)。


我认为这里还有一个误解:

我可以假设“buf”内容被复制到 POSIX 框架/网络堆栈中的内部缓冲区

这就是发送成功时发生的情况。返回成功并不意味着您的数据已被接收,甚至实际发送:它意味着操作系统已负责交付它。

所以在你的建议中,成功和失败之间没有区别。

【讨论】:

【参考方案4】:

我可以假设“buf”内容被复制到 POSIX 框架/网络堆栈中的内部缓冲区

当然不是。这就是为什么它告诉你再试一次。没有任何东西被转移到任何地方。

我可以使用“buf”向套接字发送新数据

没有。

或者EAGAIN表示后面会复制“buf”中的数据

重试发送之前,它永远不会被复制。

所以“buf”内存不应该改变?

你可以做任何你喜欢的事情,但是如果你想重试你最好不要先改变它。

我对此进行了很多搜索,但没有找到明确的答案。

难以置信。您的搜索应该从send()man 页面开始。

对于aio_write,我在手册页中找到了以下注释:

无关紧要。这个措辞是因为数据还没有被写入,但是当异步 I/O 处理它时。与非阻塞模式无关。

【讨论】:

考虑编辑它以改善语气。它在磨蚀性方面。 一方面,粗暴的语气是传达文化规范的一种非常有效的方式,例如 我们希望您阅读手册页,并说明您还研究了什么以及为什么它没有帮助 另一方面,OP 至少做了其中的一部分,而我的 Linux send 联机帮助页的本地副本信息量不是很大。 POSIX的描述稍微好一点,也许EJP的本地系统更好。 @YvetteColomb 考虑准确说明这是“在磨蚀性方面”的确切位置和方式,而不是仅仅做出没有实际价值的模糊影射。或者提供你自己的答案,这样我们就可以批评它的形式而不是它的内容。

以上是关于POSIX - 理解非阻塞发送实现的主要内容,如果未能解决你的问题,请参考以下文章

同步异步阻塞非阻塞的概念理解

概念理解同步异步阻塞非阻塞

同步与异步阻塞与非阻塞理解

同步IO,异步IO,阻塞IO,非阻塞IO的联系与区别

Netty——网络编程(非阻塞理解及代码示例)

Netty——网络编程(非阻塞理解及代码示例)