Unix socket上的原子写入?

10

我正试图在IPC机制中选择pipesunix sockets之间。
它们都支持select()epoll()函数,这很好。

现在,管道有一个4kB(截至今日)的“原子”写入,在Linux内核中得到了保证。
在使用Unix套接字的情况下是否存在这样的功能?我找不到任何明确说明此点的文档。

假设我使用Unix套接字并从客户端写入x个字节的数据。当我的服务器的select()被触发时,我能确定这些x个字节将被写入到套接字的服务器端吗?

在同一主题上,如果使用SOCK_DGRAM会确保写操作是原子的(如果可能的话),因为数据报应该是单一定义的消息吗?
如果以流传输模式使用SOCK_STREAM会有什么区别?

提前致谢。


我删除了我的回答,因为我对AF_UNIX套接字族并不是很熟悉。当我回答时,我没有想到你的意思是“Unix Socket == AF_UNIX”。man页面确实说Unix套接字的数据报是完全可靠的。所以这可能是一个选项。但由于我没有使用过它们,我会让更有经验的人来回答。 - Mark Wilkins
2个回答

11

管道

是的,非阻塞容量通常为4KB,但是为了最大的可移植性,您最好使用PIPE_BUF常量。另一种选择是使用非阻塞I/O。

有关更多信息,请参见man 7 pipe

Unix数据报套接字

使用数据报套接字上的send函数进行写入确实保证是原子操作。在Linux中,它们也是可靠的,并且保持排序。(这使得最近引入的SOCK_SEQPACKET对我来说有些令人困惑)有关此问题的许多信息都在man 7 unix中。

最大数据报大小取决于套接字。可以使用getsockopt/setsockopt获取SO_SNDBUF的值。在Linux系统上,它的范围介于2048和wmem_max之间,默认值为wmem_default。例如,在我的系统上,wmem_default = wmem_max = 112640。(您可以从/proc/sys/net/core中读取它们)关于此的大部分相关文档都在man 7 socket中,围绕着SO_SNDBUF选项。我建议您自己阅读,因为它所描述的容量翻倍行为起初可能有点令人困惑。

流和数据报之间的实际区别

流套接字只在连接状态下工作。这主要意味着它们只能与一个对等方通信。作为流,它们不能保证保留"消息边界"。

数据报套接字是未连接的。它们可以(理论上)同时与多个对等体通信。它们保留消息边界。

[我想新的SOCK_SEQPACKET介于两者之间:已连接并且保留边界。]

在Linux上,两者都是可靠的,并保持消息排序。如果您使用它们来传输流数据,它们往往表现类似。所以只需使用匹配您的流的那个,并让内核为您处理缓冲。

比较流、数据报和管道的简单基准测试:

# unix stream 0:05.67
socat UNIX-LISTEN:u OPEN:/dev/null &
until [[ -S u ]]; do :;done
time socat OPEN:large-file UNIX-CONNECT:u

# unix datagram 0:05.12
socat UNIX-RECV:u OPEN:/dev/null &
until [[ -S u ]]; do :;done
time socat OPEN:large-file UNIX-SENDTO:u

# pipe 0:05.44
socat PIPE:p,rdonly=1 OPEN:/dev/null &
until [[ -p p ]]; do :;done
time socat OPEN:large-file PIPE:p

这里没有任何显著的统计数据。我的瓶颈可能是读取大文件。


1
关于非阻塞容量,请注意非阻塞和原子性(OP所问的)是两个不同的概念。原子写操作并不意味着或导致它成为非阻塞操作,而非阻塞写操作可能是非原子性的,这意味着并非所有数据都在一个write()调用中被写入,但该调用没有阻塞线程。 - Maxim Egorushkin
我希望我能像在https://dev59.com/hHE85IYBdhLWcg3w6n90#2552464中所做的那样,包括一些实际的块大小演示,但是pv在套接字上不起作用,如果我通过它进行管道传输,则瓶颈会出现在管道上。 哦,好吧。 最后一段暂时保持未经验证状态。 - JB.
1
@Maxim,你对非阻塞/原子性区别的看法是正确的(尽管我觉得我们在引用略微不同的原子性解释)。我上次深入研究Linux源代码的记忆是它们之间的相关性比我最初想象的要更大。但由于OP没有真正说明他想要什么,或者使用了哪种Unix版本,所以我认为我会编辑以提高清晰度,并在他发表评论之前就此结束。 - JB.
从查看 https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html 和 https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html 来看,POSIX 技术上并不保证在 DGRAM/SEQPACKET(Unix)套接字上的写操作是原子性的,但任何不强制执行这两种类型原子性的实现都是如此基本上有缺陷,以至于我甚至不会尝试使用解决方法,而是在检测到时直接恐慌。 - Petr Skocik
1
“数据报必须在单个输出操作中发送,并且必须在单个输入操作中接收。”来自https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_10_06。该页面确实更详细地介绍了SEQPACKET的行为,但是具有安全可恢复的消息边界,非原子性。感谢opengroup链接,它们是这个答案背景的很好补充! - JB.

2
如果您使用的是AF_UNIX SOCK_STREAM套接字,则不能保证一次write/send()的数据会在接收端的一次read/recv()中完全接收。
另一方面,AF_UNIX SOCK_DGRAM套接字需要保留数据报边界并具有可靠性。如果send()不能以原子方式传输数据报,则应该会收到EMSGSIZE错误。关于write()会发生什么,因为手册页面没有列出它可以报告的EMSGSIZE(尽管手册页面有时不会列出所有返回的错误)。我建议您使用大型数据报来溢出接收器的缓冲区,以查看send/write()确切报告了哪些错误。
与管道相比,使用UNIX套接字的一个优点是缓冲区更大。我不记得管道内核缓冲区的限制是多少,但我记得没有足够的缓冲区并且无法增加它(它是硬编码的内核常量)。由于管道缓冲区大小不足,fast_producer_process | slow_consumer_processfast_producer_process > file && file > slow_consumer_process慢了数个数量级。

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