【问题标题】:Atomic write on an unix socket?在 unix 套接字上进行原子写入?
【发布时间】:2011-01-12 14:10:49
【问题描述】:

我正在尝试在 管道unix 套接字 之间进行选择以实现 IPC 机制。
两者都支持select()epoll() 功能,非常棒。

现在,管道有 4kB(截至今天)“原子”写入,这是由 Linux 内核保证的。
在 unix 套接字的情况下是否存在这样的功能?我找不到任何明确说明这一点的文件。

假设我使用 UNIX 套接字并从客户端写入 x 字节的数据。当我的服务器的select() 破解时,我确定这些 x 字节将写入套接字的服务器端吗?

在同一主题上,使用 SOCK_DGRAM 是否会确保写入是原子的(如果这样的保证是可能的),因为数据报应该单个明确定义的消息?
那么使用 SOCK_STREAM 作为传输模式会有什么不同呢?

提前致谢。

【问题讨论】:

  • 我删除了我的答案,因为我对 AF_UNIX 套接字系列不太熟悉。当我回答时,我不认为您的意思是“Unix Socket == AF_UNIX”。 man page 确实说带有 Unix 套接字的数据报是完全可靠的。所以这可能是你的一个选择。但由于我没有使用它们,我将它留给更有知识的人来回答。

标签: sockets unix atomic


【解决方案1】:

管道

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

比您想知道的更多信息man 7 pipe

Unix 数据报套接字

确实保证在数据报套接字上使用send 系列函数写入是原子的。在 Linux 的情况下,它们也是可靠的,并且保持顺序。 (这让我最近对SOCK_SEQPACKET 的介绍有点困惑)man 7 unix 中有很多关于此的信息。

最大数据报大小取决于套接字。在SO_SNDBUF 上使用getsockopt/setsockopt 访问它。在 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

这里没有统计学意义。我的瓶颈可能是读取大文件。

【讨论】:

  • 关于非阻塞容量,请注意非阻塞和原子(OP 要求的)是正交的东西。原子写入并不意味着或导致它是非阻塞的,并且非阻塞写入可能是非原子的,这意味着并非所有数据都已在一次 write() 调用中写入,但该调用并未阻塞线程。
  • 我希望我可以像在stackoverflow.com/questions/2552402/cat-file-vs-file/… 中那样包含一些实用的块大小演示,但是 pv 在套接字上不起作用,如果我通过它进行管道传输,瓶颈就会出现在管道上。那好吧。最后一段暂时未得到验证。
  • @Maxim 您对非阻塞/原子性的区别是正确的(尽管我觉得我们指的是对原子性的略微不同的解释)。我对最后一次深入研究 Linux 源代码的记忆是,它们比我最初想象的更相关。但由于 OP 并没有真正说明他追求的是什么,或者他使用的是什么 Unix 风格,我想我会为了清楚起见进行编辑,并在他完成之前将其保留。
  • 从查看 pubs.opengroup.org/onlinepubs/9699919799/functions/send.htmlpubs.opengroup.org/onlinepubs/9699919799/functions/write.html 看来,POSIX 并没有技术上保证 DGRAM/SEQPACKET (unix) 套接字上的写入是原子的,但是任何实现不强制这两种类型的原子性被彻底破坏,我什至不会尝试做一个解决方法,如果我发现它只会恐慌。
  • @PSkocik “数据报必须在单个输出操作中发送,并且必须在单个输入操作中接收。”来自pubs.opengroup.org/onlinepubs/9699919799/functions/… 该页面确实更清楚地说明了 SEQPACKET 行为,虽然:非原子的,但具有可安全恢复的消息边界。感谢 opengroup 链接,它们是这个答案上下文的一个很好的补充!
【解决方案2】:

假设我使用了一个 UNIX 套接字,我写了 x 来自我的客户的数据字节。我是不是 确保这些 x 字节将是 写在服务器端 当我的服务器的select() 时的套接字 裂缝?

如果你使用AF_UNIXSOCK_STREAM套接字,则没有这样的保证,即写入一个write/send()的数据可能需要在接收端调用多个read/recv()

在同一主题上,将使用 SOCK_DGRAM 确保写入是 原子的(如果这样的保证是 可能),因为数据报是 应该是单一的明确定义的 消息?

另一方面,AF_UNIX SOCK_DGRAM 套接字需要保留数据报边界并保持可靠。如果send() 无法自动传输数据报,您应该会收到 EMSGSIZE 错误。不确定write() 会发生什么,因为手册页没有说它可以报告 EMSGSIZE(尽管手册页有时不会列出所有返回的错误)。我会尝试用大数据报溢出接收器的缓冲区,以查看 send/write() 报告的确切错误。

使用 UNIX 套接字而不是管道的一个优点是更大的缓冲区大小。我不记得确切的管道内核缓冲区的限制是什么,但我记得它没有足够的空间并且无法增加它(它是一个硬编码的内核常量)。由于管道缓冲区大小不足,fast_producer_process | slow_consumer_processfast_producer_process > file && file > slow_consumer_process 慢几个数量级。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-06
    • 2017-07-31
    • 2017-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-14
    • 1970-01-01
    相关资源
    最近更新 更多