Linux中是否有sendfile的异步版本?

4
io_getevents通知机制乍一看似乎非常能胜任,因此我希望找到可以与之配合使用的东西。但是我还没有找到合适的东西。在Windows上,这很容易:只有TransmitFile,它可以异步(重叠)工作,并且如果需要的话,可以使用一些通知机制(IOCP、事件)。在Linux上肯定也有相应的等效物,对吧?或者说,为了让我的问题更具体一些,我该如何在Linux上创建一个高效的文件服务器?

如果套接字是非阻塞的,那么sendfile也是如此(它将报告在套接字的“缓冲区”中计划发送多少数据)。您需要轮询套接字以查看何时可以继续sendfile操作(参见epoll)... 或者更好的办法是使用一个为您完成此操作的库。 - Myst
@Myst Mh,当我想到异步I/O时,我会考虑到那些可以在任意时间开始并在完成时得到通知的操作。但是使用epoll+sendfile,我必须首先等待发送缓冲区可用,然后调用sendfile将一定数量的数据复制到这些缓冲区(同步执行!),反复进行该过程。 - purefanatic
1
另外,我读到了即使在使用非阻塞套接字时,sendfile可能仍然会阻塞的情况,并且可以通过使用readahead来解决这个问题:http://brad.livejournal.com/2228488.html。这引入了更复杂的应用程序设计和更多的延迟,因为在实际工作之前需要进行多次上下文切换。我并不真正满意整个“非阻塞”方法。 - purefanatic
@Myst 好的,但是sendfile文档有些误导。它明确说:“如果传输成功,返回写入到out_fd的字节数。”此外,非阻塞的send/recv必须同步复制,没有其他方法。但这不是重点。例如,我想同时进行多个发送操作。我认为使用非阻塞套接字+epoll是不可能的,对吗?使用真正的异步I/O,我可以排队一些头部,然后是实际的文件数据。操作系统可以在适当时候开始发送我的头部,并及时预取文件数据。 - purefanatic
是的,你可能是对的。我发现 epoll 在应用设计和性能方面都有一定的局限性。我觉得内核异步 I/O API 更吸引人,但似乎还不支持 sendfile。 - purefanatic
显示剩余2条评论
1个回答

5

哎呀,对于您来说,在Linux上没有什么是容易的,几乎任何事情都可能在错误的情况下被阻塞(甚至包括io_submit)。回答您的问题(标题和正文中):

这就是现实...

未来(2020年及以后)的解决方案

有一个建议是,一些未来的Linux内核(晚于5.5,因为在撰写本文时已经更新到了5.5-rc7)可以通过io_uring实现异步sendfile,如果io_uring获得对splice()的支持...


你链接的博客文章似乎建议将阻塞操作转移到用户空间线程池中,这与Arvid在2012年使用libtorrent时做的事情是一样的,因为没有更好的选择。这正是我想尽一切努力避免的,而是希望使用一些更高效的API。我只是希望Linux在6年多的时间里能够改进这些问题。我的意思是,如果FreeBSD可以做到,为什么Linux不能呢?两者都是POSIX,所以我觉得它们有相似的局限性。 - purefanatic
我希望io_submit只在极端情况下阻塞,比如I/O请求队列或完成队列已满。也就是说,在这种情况下,你确实需要等待任务完成。 - purefanatic
@purefanatic 在Linux中缺乏一个出色的普遍异步I/O框架只是一个缺陷 - 流行并不意味着你在每个类别中都是最好的。POSIX从未强制要求使用异步框架,因此这与此无关。当您进行非缓存的缓冲读取时,io_submit可能会阻塞,这并不算是什么奇特或极端的情况。 - Anon
谢谢你提供关于io_submit的参考!我认为POSIX从未强制要求一个合适的异步框架,这正是Linux没有考虑异步I/O的确切原因之一。这就是为什么我称之为一种劣势的原因。 - purefanatic
@purefanatic 不客气。好的,是我应该更谨慎地表达。POSIX确实规定了一组AIO操作(参见http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/aio.h.html),但是a)它是可选的,b)在Linux上,是glibc使用线程池来实现这些函数,c)POSIX中没有sendfile。我应该说的是,我想表达的是,没有一个适用于大多数现有操作的POSIX定义的AIO框架,而不是定义一些新的异步操作。 - Anon
很遗憾,我对io_uring的“解决方案”仍然不太满意,即在中间放置一个管道并将数据拼接到它和从它读取。如果我理解正确的话,我需要提交一个splice到管道中,等待它完成,然后再提交另一个splice从管道传输到接收端,并且如果文件太大无法在第一次调用中完全拼接,则需要重新开始。而且我还需要分配和释放一个管道,或者维护一个管道池。这似乎比Winsock的TransmitFile更差,后者能够在单个系统调用中发送高达2 GiB的数据。 - purefanatic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接