【问题标题】:Multiple child processes reading/writing on the same pipe多个子进程在同一管道上读/写
【发布时间】:2011-08-22 19:38:40
【问题描述】:

我目前正在学习在 Linux 环境中使用 C 进行套接字编程。作为一个项目,我正在尝试编写一个基本的聊天服务器和客户端。

目的是让服务器为每个连接的客户端派生一个进程。

我遇到的问题是读取一个孩子的数据并将其写入所有连接的客户端。

我试图通过在等待数据到达套接字或读取管道末端的子进程中循环调用 select 来实现这一点。如果它到达套接字,则想法是它写入管道的写入端,这导致 select 返回管道的读取端准备好读取。

由于该管道在所有子节点之间共享,因此每个子节点都应读取管道上的数据。这不起作用,因为数据管道似乎不能被每个子进程同时读取,并且在调用读取时“错过”数据块的子进程。

以下是执行此操作的子进程中的代码:

for( ; ; )
{
  rset = mset;
  if(select(maxfd+1, &rset, NULL, NULL, NULL) > 0)
  {
    if(FD_ISSET(clientfd, &rset))
    {
      read(clientfd, buf, sizeof(buf));
      write(pipes[1], buf, strlen(buf));
    }
    if(FD_ISSET(pipes[0], &rset))
    {
      read(pipes[0], buf, sizeof(buf));
      write(clientfd, buf, sizeof(buf));
    }
  }
}

我假设我目前使用的方法根本行不通。从客户端接收的消息是否可以通过 IPC 写入所有其他连接的客户端?

谢谢

【问题讨论】:

    标签: c sockets


    【解决方案1】:

    为了解决孩子从管道中读取的数据超出其应有的数据的问题(进而使另一个孩子在尝试从空管道中读取数据时“卡住”),您可能应该考虑使用任一 POSIX 消息在父进程和单个子进程之间使用队列或单个管道,而不是在父进程和子进程之间进行通信的单个全局管道。就目前而言,当服务器写入管道以与其子级通信时,它实际上无法准确控制在任何给定时间从管道中读取哪个子级,因为操作系统对进程的调度是不确定的.换句话说,如果没有某种类型的同步机制或读/写障碍,如果服务器写入管道,您的代码中没有任何内容可以阻止一个孩子“跳过”读取,以及第二个孩子做双重读取,让另一个应该从服务器获得广播数据的孩子饿死,因此被阻止。

    解决此问题的一种简单方法是在父级和单个子级之间共享一个私有管道。因此,在服务器中,子进程可以从客户端读取数据,将该数据发送回父进程,然后父进程可以使用它为所有子进程累积的整个管道描述符列表,写回每个单独的子进程然后将广播消息发送回每个客户端。没有孩子会“饿死”数据,因为另一个子进程不可能重复读取。每个管道上只有一个读写器,通信是确定性的。

    如果您不想为服务器父进程中的每个子进程处理多个管道,则可以使用使用 POSIX 消息队列的全局消息队列(在 mqueue.h 中找到)。使用这种方法,如果一个孩子抓取了一条它不应该拥有的消息(即,您需要传递一个包含某种类型的 ID 值的struct),它会将消息放回队列中并尝试阅读另一条消息......这在速度方面不如直接管道方法那么有效,但它可以让您回写一条未指定给当前孩子的消息,而不会出现全局管道或先进先出机制。

    【讨论】:

    • 顺便说一句,管道上有多个读取器的另一个问题是管道上的读取不是原子的......换句话说,如果你有一个read 调用,你可能不会得到整个消息一次致电read。如果与多个读取器发生争用,则可能会产生数据损坏问题。虽然write 最大大小为PIPE_BUF 的操作是原子的,但对read 的调用并不意味着如果您正在读取多字节或可变长度数据,单个管道上的多个读取器可能会非常痛苦-字符串。
    • 啊,好吧,如果一个在 FD 上调用 read 的进程必须循环并再次调用 read 以获取其余数据,那么在这个间隙中,另一个进程是否有可能“窃取”读取并锁定另一个?
    • 嗯,有两种可能性... 1) 两个进程试图从管道中读取数据。操作系统调度第一个进程,该进程进行读取。然后由于两个进程都在循环中,它会跳过第二个进程,只允许第一个进程再次读取,从第二个进程“窃取”数据,现在第二个进程卡住了。 2)第一个进程执行read,但没有获得所有数据。所以它的数据被破坏了。然后第二个进程执行read,但数据损坏,因为它没有收到它的消息,而是得到第一个进程的消息的一部分,以及它自己的一部分。
    • 如果场景 #1 听起来令人困惑,请考虑在子进程中调用 if(FD_ISSET(pipes[0], &rset))read(pipes[0], buf, sizeof(buf)); 之间的时间,操作系统可能会使进程进入睡眠状态。操作系统还可以让进程保持休眠状态,只要它愿意。因此,在这两行之间,如果有一堆其他进程试图从同一个管道中读取数据,那么最终这些进程可能会读取管道中的所有数据,然后当操作系统唤醒当前进程备份时,它只会阻塞read,直到将新消息写入管道。
    • 感谢您提供的信息,在所有进程之间使用单个管道是无法管理的,而且在最不可靠的情况下。老实说,我什至不知道消息队列,所以我会考虑使用它们作为替代方案。
    【解决方案2】:

    写入管道的每个数据字节都将被读取一次。它不会复制到管道读取端打开的每个进程。

    如果您希望将数据复制到多个目标进程,则必须显式复制数据。例如,您可以有一个“主”进程,该进程具有往返于每个“从”进程的管道。当一个从站想要向其他从站广播消息时,它会将消息发送到主进程,主进程会循环并将其写入到通往其他从站的每个管道。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-13
      • 2017-09-19
      • 2020-11-04
      • 1970-01-01
      • 2015-05-04
      • 1970-01-01
      相关资源
      最近更新 更多