【问题标题】:Forks and Pipes in C UNIXC UNIX 中的分叉和管道
【发布时间】:2010-12-14 21:29:58
【问题描述】:

我不确定我是否在这里吠叫了正确的树...但是就这样吧。

我正在尝试将数据从我的父进程传递给所有子进程。它是一个简单的服务器程序,基本上会保留一个已连接客户端的列表,然后将已连接客户端的路由表发送给每个客户端。这最终将包含有关每个客户端的信息结构......但现在我只想让每个分叉进程从父进程那里获取相同的信息。

在父进程中,首先我设置我的管道并将它们设置为非阻塞(当管道中没有任何新数据可用时)。与客户端建立连接后,条目变量的数量会增加以反映此新连接。然后,我将一个子进程派生到一个新函数,并使用新的表条目数更新我的管道数组(我现在有 10 个管道,看看是否需要为每个子进程保留一个单独的管道)。

            pid_t pid;
    int numchildren;

    int i, n;

    /* Create the pipes. */
    for(i = 0; i < 10; i++)
    {
        if (pipe (mypipe[i]))
        {
        fprintf (stderr, "Pipe failed.\n");
        return EXIT_FAILURE;
        }
    }

    for(i = 0; i < 10; i++)
    {
        for(n=0; n<2; n++)
        {
          // Get previous flags
          int f = fcntl(mypipe[i][n], F_GETFL, 0);
          // Set bit for non-blocking flag
          f |= O_NONBLOCK;
          // Change flags on fd
          fcntl(mypipe[i][n], F_SETFL, f);
        }
        //close(mypipe[i][0]);
    }

        pid = fork();

    if (pid == (pid_t) 0)
    {
        close (mypipe[numentries-1][1]);
        recievecmds(new_fd, mypipe[numentries-1][0]);
        close(new_fd);
        return EXIT_SUCCESS;
    }

else if (pid < (pid_t) 0)
{
    fprintf (stderr, "Fork failed.\n");
    return EXIT_FAILURE;
}
else
{
    sprintf (buf,"%d",numentries);
    for(i = 0; i < 10; i++)
        write(mypipe[i][1], buf, strlen(buf));
    memset(&buf, 0, sizeof buf);
}

然后我尝试在 recievecmds() 函数中读取管道中的内容:

nbytes = read(mypipe[childindex][0], buf, sizeof(buf));

第一个连接的客户端告诉我 numentries = 1,第二个客户端告诉我 numentries = 2,依此类推。我的意思是我什至看不到管道的意义,因为似乎无论我放入管道中的什么,我都可以将它传递到我在 fork 上调用的函数中。我会以错误的方式解决这个问题吗?试图弄清楚这一点非常令人沮丧。如何让我的所有子进程从我的父进程同时更新?

非常感谢您。

edit - 我的主要问题是我每次都在无限循环中重新声明管道。非常愚蠢的错误,立即意识到这可能是我问题的根源。然而,虽然现在第一个子/管道组合包含正确的数据......第二个没有。我会看看我是否可以自己解决这个问题,谢谢您的建议!

当然,现在我遇到了问题,因为我手动选择了一个选项来从管道中获取数据。我将不得不想出一种方法,要么在每次更新时获取所有管道的数据,要么确保只获取最新数据(可能一次只获取一个字符)。

感谢你们容忍我!我很抱歉没有发布整个程序......但是有很多。我绝对应该提到我有一个无限循环。

【问题讨论】:

  • 您提到,当您可以进行函数调用时,您不确定为什么需要管道。请记住,每次 fork 一个新进程时,它都会获得父级数据的副本;不同的孩子得到不同的副本。而且,这些都是单独的过程;不要让父母和孩子的代码位于同一个文件中这一事实欺骗了您。使用 IPC 很困难,尤其是当您是新手时 - 祝您好运! :-)
  • @EmerickRogul:我喜欢以积极、鼓励的方式帮助他人。继续努力吧。 :)

标签: c ipc fork pipe


【解决方案1】:

各种观察

  1. 不要让管道不堵塞;您希望孩子在没有数据时阻止。至少,在设计的早期阶段;稍后,您可能希望在没有数据等待时让他们继续工作。
  2. 你需要小心你的管道。父母需要 10 个管道,每个孩子一个。但它只需要管道的写端,不需要读端。
  3. 每个孩子都需要一根烟斗,用于阅读。任何多余的管道(例如,父级在分叉第 N 个子级之前已经打开的管道的写入端)都需要关闭。
  4. 您可以考虑使用线程——在这种情况下,您或许可以将数据传递给孩子。但从长远来看,您似乎会定期将数据传递给孩子,然后您需要一种机制来获取数据给他们(函数调用除外)。
  5. 只要您仔细注意正在使用的文件描述符,管道就“简单”了。关闭所有不需要的描述符。
  6. 父级必须循环所有十个管道,向每个管道写入相同的数据。
  7. 还需要考虑如果孩子退出了该怎么办。它应该关闭管道(不再使用),并决定是否启动一个新的孩子(但它如何确保新的孩子拥有它需要的所有累积信息?)。
  8. 注意 SIGPIPE - 可能安装处理程序,或者可能使用 SIG_IGN 并检测写入错误而不是信号。

工作代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

enum { NUM_CHILDREN = 10 };
enum { NUM_MESSAGES = 10 };

static int write_pipes[NUM_CHILDREN];
static int n_pipes;

static void be_childish(int *pipe)
{
    int i;
    char buffer[32];
    int nbytes;
    int pid = getpid();
    close(pipe[1]);
    for (i = 0; i < n_pipes; i++)
        close(write_pipes[i]);
    printf("Child %d\n", pid);
    while ((nbytes = read(pipe[0], buffer, sizeof(buffer))) > 0)
    {
        printf("Child %d: %d %.*s\n", pid, nbytes, nbytes, buffer);
        fflush(0);
    }
    printf("Child %d: finished\n", pid);
    exit(0);
}

int main(void)
{
    pid_t pid;
    int i, j;

    /* Create the pipes and the children. */
    for (i = 0; i < NUM_CHILDREN; i++)
    {
        int new_pipe[2];
        if (pipe(new_pipe))
        {
            int errnum = errno;
            fprintf(stderr, "Pipe failed (%d: %s)\n", errnum, strerror(errnum));
            return EXIT_FAILURE;
        }
        if ((pid = fork()) < 0)
        {
            int errnum = errno;
            fprintf(stderr, "Fork failed (%d: %s)\n", errnum, strerror(errnum));
            return EXIT_FAILURE;
        }
        else if (pid == 0)
        {
            be_childish(new_pipe);
        }
        else
        {
            close(new_pipe[0]);
            write_pipes[n_pipes++] = new_pipe[1];
        }
    }

    for (i = 0; i < NUM_MESSAGES; i++)
    {
        char message[30];
        int len;
        snprintf(message, sizeof(message), "Message %d", i);
        len = strlen(message);
        for (j = 0; j < n_pipes; j++)
        {
            if (write(write_pipes[j], message, len) != len)
            {
                /* Inferior error handling; first failure causes termination */
                fprintf(stderr, "Write failed (child %d)\n", j);
                exit(1);
            }
        }
        sleep(1);
    }
    printf("Parent complete\n");

    return 0;
}

【讨论】:

  • 感谢乔纳森的快速反应。 1)我暂时让它们不阻塞的原因是,如果客户端尝试刷新路由器表并且管道上没有任何数据等待(没有任何新连接,所以管道还没有填充一个新数字)它只会读取任何内容,然后继续。你认为这可能会导致我的问题吗?似乎管道只在分叉处更新了一次。我错过了什么?
【解决方案2】:

我建议使用共享内存段。您的父进程和子进程可以映射同一个文件,并对其进行读/写状态。这可以是实际文件,也可以是匿名内存段。这正是 Apache 对他们的ScoreBoardFile 所做的。

【讨论】:

  • 不错的主意 - 尤其是因为以后很容易添加额外的子进程。唯一的遗留问题是 (a) 确保孩子在父母写作时不阅读,以及 (b) 在发生变化时通知孩子。
  • 可怕的想法,因为 Jonathan Leffler 提到的原因,稍后添加额外的子进程很困难。
猜你喜欢
  • 1970-01-01
  • 2013-11-18
  • 1970-01-01
  • 1970-01-01
  • 2013-11-10
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
相关资源
最近更新 更多