我编写了一个服务器,它打开一个命名管道(它会阻塞,所以它会等待客户端连接),然后定期向管道写入一些内容。客户端打开管道,从中读取并处理数据。但是,由于我无法控制的情况,客户端经常退出并在此后不久再次重新启动。
当服务器想要在没有读取器连接到管道的短时间内写一些东西时,这会导致问题:服务器收到一个 SIGPIPE 并退出。我可以忽略信号,但我不想丢失数据:理想情况下,服务器会等到客户端重新连接到管道后再写入数据。在此写入期间服务器阻塞是没有问题的。
使用write()我可以尝试 0 字节写入并检查EPIPE错误以检测是否有客户端连接。但是我怎样才能阻止直到客户端连接(除了稍微睡一觉,然后再次尝试写入)?
还是有另一种更好的方法来实现这一目标?
事实证明,与我对上述原始问题的评论相反,有一个简单的解决方案。
此解决方案假定同一 FIFO 的所有读取器和所有写入器共享内核缓冲区。这应该是实现 FIFO 的最合乎逻辑和最直接的方式(考虑到它们的行为),所以我确实希望所有提供 FIFO 的系统都以这种方式运行。然而,这只是我的假设,并没有任何保证。我在相关的 POSIX 标准中没有发现任何支持或反驳这一点的内容。如果您发现其他情况,请执行管道。
过程很简单:
当客户端意外消失时,写入器会再次打开 FIFO,而不会先关闭原始描述符。这open()将阻塞,直到有新的读取器可用,但由于原始文件描述符仍处于打开状态,因此新读取器可以使用已缓冲在 FIFO 中的数据。如果open()成功,作者只需关闭原始描述符,然后切换到使用新描述符。
如果内核结构是共享的,则 FIFO 缓冲区状态在写入器描述符之间共享,新读取器将能够读取前一个读取器未读取的内容。
(请注意,编写器不知道客户端更改之间缓冲的数据量,因此不知道数据流中发生切换的点。)
我已经验证了这个微不足道的策略适用于 Ubuntu x86_64 上的 Linux 3.8.0-35 通用内核,以及 x86_64 上的 2.6.9-104.ELsmp。
但是,我仍然完全同意接受数据丢失或更改协议,正如 Basile Starynkevitch 在对原始问题的评论中所建议的那样。
就我个人而言,我发现 Unix 域套接字(绑定到路径名,比如/var/run/yourservice/unix)是一个更好的选择,因为它允许多个并发客户端而不会损坏数据(与 FIFO 不同),并且是一个更健全的协议。
我更喜欢 Unix 数据报套接字,在每个数据报的开头有一个序列号和数据报长度。(长度有助于客户端验证它读取整个数据报;我真的不希望任何操作系统截断 Unix 数据报。)
通常,作者向每个客户端发送一些数据报,并在发送新数据报之前等待确认。处理完数据报后,客户端通过向写入者发送序列号来确认数据报。(请记住,这些是套接字,因此通信是双向的。)这允许编写器为每个客户端保留一些数据报,并且客户端以正确的(序列号)顺序或无序处理数据报,使用多线程。
重要的一点是每个数据报只有在处理后才被确认,而不是在收到后立即确认。
(除了 reader->writer "acks" (acknowledgements),我还支持“please resend”响应,以防客户端使用太小的缓冲区来接收数据报。或者由于其他一些原因将其丢在地板上. 甚至可能对客户端不知道该怎么做的数据报“恶心”。)
如果客户端消失,写入者知道所有未确认的数据报尚未被客户端处理,并且可以将它们重新发送到另一个连接的客户端或未来的客户端。
问题?
| 归档时间: |
|
| 查看次数: |
1643 次 |
| 最近记录: |