POSIX POLLOUT 和阻塞文件描述符

POSIX POLLOUT and blocking file descriptors

根据the POSIX poll docsPOLLOUT标志表示"Normal data may be written without blocking."。但是有多少数据?是否有任何固定的保证,或者有什么方法可以找出有多少缓冲区space?

我正在写入管道和套接字。通常,我的程序不会得到 EINTR,因为所有预期的信号都是通过 signalfd/kqueue 处理的;据我所知,这意味着我可以预期 write 总是阻塞,直到所有请求的数据都已传输。

我不确定我是否想要这个。理想情况下,我只想在缓冲区中写入与 space 一样多的数据,以便代码可以尽快返回轮询循环而不会阻塞。 (如果还有数据要写入,我的代码可以再次检查同一个 FD 上的 POLLOUT,并在 space 可用时再次执行相同的操作。)

有什么方法可以使它正常工作吗? poll 似乎可以很好地从阻塞的 pipe/socket FD 中读取数据:设置 POLLIN 标志后,您可以读入缓冲区,并且可以在不阻塞的情况下获得尽可能多的可用数据。但是开始写起来有点不方便了!我错过了什么吗?

或者这只是想让我使用 O_NONBLOCK

我正在研究 OS X 和 Linux。

从表面上看,您最多可以保证能够写入 1 个字节。

切换到非阻塞模式也无济于事。当然,您不会阻塞,但您必须弄清楚如何处理非阻塞写入未写入的任何数据。无论文件描述符的阻塞模式如何,poll() 都会为您提供相同的结果。

您可以尝试几个替代方案,异步 IO 或 ZeroMQ 等框架。

异步 IO

asynchronous IO,这实际上归结为让一堆后台线程为您编写。很好,但是您随后必须管理数据生命周期/所有权问题。在您被告知异步写入已完成之前,您必须保留正在写入的任何内容并且保持不变。至少可以这么说。

框架

另一种选择是消息框架,例如来自 ESA 的 ZeroMQ. There's others - nanomsg (written by the same guy who did ZeroMQ), DDS, Corba (if you dare), TASTE

直接更换

这些都有不同的品质,但 ZeroMQ 和 nanomsg 尤其被设计为将代码从使用标准套接字和管道 API 转换为使用它们自己的(尤其是 nanomsg)的最不痛苦的方式。

消息,不是流

所有这些都是面向消息的,而不是面向流的,它们都很好地为您管理通过某种连接传递数据的痛苦业务,并解决了数据生命周期问题。

底层传输

ZeroMQ 和 nanomsg 将跨管道、套接字、共享内存等工作。当您的代码等待输入时,它们也可以将普通文件描述符合并到它们的 poll() 等价物中(如果您有,则很有用,比如说, 串口的 fd 也可以收听)。

与异步IO的比较

这类框架和async io的区别如下。使用框架,连接另一端的软件也必须使用相同的框架,否则它们将无法通信。使用aio,另一端仍然可以使用普通的同步socket函数调用,因为从根本上来说,它仍然只是一个流连接。

这些框架比 aio 更不透明。谁知道框架内部进行了多少数据复制?这通常无关紧要,假设您的 RAM 比您的网络快得多。

0MQ/nanomsg 与其余

ZeroMQ/nanomsg 和 DDS/Corba/TASTE 之类的东西的区别在于后者还包含序列化。它们允许您以独立于平台的描述语言指定消息结构,这使 'compiled' 成为您使用的任何源代码语言(C、C#、JAVA 等)。这很自然,有点像声明 C 结构。这允许您在连接的另一端拥有完全不同的平台和编程语言,他们仍然会交谈。 DDS 和 Corba 为此使用 IDL,TASTE 使用 ASN.1(在所有可能的方面都优于)但 TASTE 有点难以上手。

您可以使用 ZeroMQ/nanomsg 实现相同的效果,例如,使用 Google 协议缓冲区或 ASN.1(或任何 many available )序列化数据和 ZeroMQ/nanomsg 将串行器生成的字节流作为消息传输。

唯一保证你至少能写1个字节;然而,通常这会更多,因为大多数操作系统都很聪明地避免 silly window syndrome

I'm not sure I want this. Ideally, I'd like to just write as much data as there's space in the buffer for, so that the code can get back to the poll loop ASAP without blocking.

然后使用O_NONBLOCK。套接字的 write() 将 return 写入内部缓冲区的字节数。如果您尝试写入 10,000 个字节并 write() returns 2,000,您仍然有 8,000 个字节。它需要更多的内部管理(好吧,一个指针和一个整数)但这是保持内核缓冲区满的最有效方法。

请注意,如果您完成写入,请清除 events 标志中的位,否则您的 poll() 调用将保持 return 与 "write more data!" 同步。