GNU C使用管道进行多工艺处理

时间:2014-12-27 22:39:34

标签: c pipe multiprocessing fork posix

我在大学里被告知在后台处理流程的最简单方法是使用管道{{1}挂起 子/父进程 的运行功能。 老实说,我已经做了两个星期的家庭作业,我无法解决异步过程处理问题。 我最小化了我的代码,编写了一个处理2个管道的子进程,并使用read()函数阻止了父进程和子进程。您可以在下面找到我的代码的当前状态:

read()

错误如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char pipeMessage[100];

char* readFromPipe(int pipe)
{
    pipeMessage[0] = '\0';
    read(pipe, pipeMessage, sizeof(pipeMessage));
    fflush(NULL);
    return pipeMessage;
}

void writeToPipe(int pipe, char *input)
{
    char text[strlen(input) + 1];
    strncpy(text, input, (int)strlen(input));
    strncat(text, "\0", 1);
    printf("TEXT: %s\n", text);
    write(pipe, text, sizeof(text));
    fflush(NULL);
}

int main(void)
{
    int pipes[2][2];
    pid_t pid;

    if(pipe(pipes[0]) < 0 || pipe(pipes[1]) < 0)
    {
        printf("[ERROR] create pipes\n");
        return -1;
    }

    printf("[PARENT] Create child1\n");
    if((pid = fork()) < 0)
    {
        printf("[ERROR] Fork error\n");
        return -1;
    }

    if(pid == 0)
    {
        // Child code
        close(pipes[0][0]);
        writeToPipe(pipes[0][1], "TEST MESSAGE");
        printf("[CHILD1] pipe message: %s\n", readFromPipe(pipes[1][0]));
        writeToPipe(pipes[0][1], "-1");
    }
    else if(pid > 0)
    {
        // Parent code
        close(pipes[0][1]);
        close(pipes[1][0]);
        char *message;
        do
        {
            message = readFromPipe(pipes[0][0]);
            printf("[PARENT] pipe message: %s\n", message);
            writeToPipe(pipes[1][1], "-1");
        }
        while(atoi(message) != -1);
    }
    return 0;
}

我尝试使用信号实现此进程处理,但在最终的应用程序中,我将需要3个不同的子进程,异步运行会导致信号处理问题。 我也尝试在网上找到一个教程,但每个多处理主题都涵盖了从父到子的简单单一消息解决方案,反之亦然。但是我在父进程中需要一个基于字符的菜单,所以孩子们应该连续等待父信号/消息,而父母也需要等待孩子完成实际任务。 请帮帮我,因为我真的卡住了。如果您有任何正常的流程处理解决方案,请告诉我,因为我知道这段代码很糟糕。唯一的原因是缺乏正确的教程。 提前谢谢。

2 个答案:

答案 0 :(得分:1)

好的,我有一个专业用于嵌入式解决方案的模板。使用这个,我创建了一个工作解决方案,它包含客户端循环,但只声明了服务器循环。应该清楚如何制定解决方案。

应该清楚如何将多个读/写器添加到select调用中。注意使用SOCK_DGRAM。除了socketpairs之外,还可以添加管道(这对于从流程到流程的流式传输原始数据是首选)。我希望你能从中学到一些东西。我们应该删除它(有时)。它需要更多的工作才能完成(服务器循环)

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <cstring>  // strerror
#include <unistd.h> // pipe
#include <fcntl.h>

#include <stdint.h>
#include <algorithm>

#define CallErrExit(fun, arg, retval)  {    \
    if ((fun arg)<0) {              \
      FailErr(#fun);                \
      return retval;                \
    }                       \
  }

#define FailErr(msg) {              \
    (void)fprintf(stderr, "%s, %s(%d)\n",   \
          msg, strerror(errno), errno); \
    (void)fflush(stderr);}

const int parentsocket = 0;
const int childsocket = 1;

int disk_main(int comm_server[]);
int server_main(int comm_disk[]);

int main(int argc, char* argv[]) {

  int status; // Status parameter used waitpid

  // Communication sockets
  int comm_server_disk[2];

  // Process Id's
  pid_t proc_server;
  pid_t proc_disk;

  if (argc < 2) {
    fprintf(stderr,"ERROR, no port provided\n");
    return EXIT_FAILURE;
  }

  proc_server = getpid();

  int socket;

  // SOCK_DGRAM are connectionless as opposed to SOCK_STREAM or SOCK_SEQPACKET
  CallErrExit(socketpair, (AF_UNIX, SOCK_DGRAM, 0,
                   comm_server_disk), EXIT_FAILURE);

  CallErrExit(proc_disk = fork,(),EXIT_FAILURE);

  if (proc_disk == 0) {
    // Disk process
    proc_disk = getpid();

    // TODO: Try the alternative, use the names '/proc/<process id>/fd/
    printf("disk_main started with sockets:\n"
           "\tserver->disk: /proc/%d/fd/%d\n"
           "\tdaup->disk: /proc/%d/fd/%d\n",
           proc_disk, socket+1,
           proc_disk, socket+3);

    /*
     * Closing comm_server_disk[1] before function entry
     */
    close(comm_server_disk[childsocket]); // Write to server

    return disk_main(comm_server_disk);
  }
  else {

    // Server process, closes comm_server_disk[0]
    server_main(comm_server_disk);

    // Never reached

    // calling process sets SIGCHLD to SIG_IGN, ECHILD is set
    CallErrExit(waitpid,  (proc_disk,&status,0), EXIT_FAILURE);
    return EXIT_SUCCESS;
  }
}

typedef struct internal_cmd {
  char cmd[32];
} internal_cmd_t;

enum e_cmd {
  eExit = 0x01,
};

// TODO: Some map between enums and cmd[32]

int disk_main(int comm_server[]) {

  char buf[1024];

  int select_width;
  fd_set rfds, wfds, efds;

  int next_select_width;
  fd_set next_rfds, next_wfds, next_efds;

  int n_fds;

  struct timeval timeout = { 10, 0 };

  FD_ZERO(&next_rfds);
  FD_ZERO(&next_wfds);
  FD_ZERO(&next_efds);

  FD_SET(comm_server[0], &next_rfds);

  // Default: is blocking, but be sure
  fcntl(comm_server[0],
        F_SETFL, fcntl(comm_server[0], F_GETFL, 0) & ~O_NONBLOCK);

  // Add other file descriptors
  int ofd = comm_server[0];
  next_select_width = std::max(comm_server[0],ofd) + 1;

  do {
    // Update read/write state
    select_width = next_select_width;
    rfds = next_rfds;
    wfds = next_wfds;
    efds = next_efds;

    // Wait for interrupt
    n_fds = select(select_width,&rfds, &wfds, &efds, &timeout);

    if (n_fds < 0) {
      fprintf(stderr,"ERROR\n");
      exit(1);
    }

    if (FD_ISSET(comm_server[0], &wfds))
      printf("Disk process can write to server\n");

    if (FD_ISSET(comm_server[0], &rfds)) {
      printf("Disk process received message from server\n");
      int rv = recv(comm_server[0], buf, sizeof(buf), MSG_DONTWAIT);
      if (rv < 0) {
        printf("Disk process - %s(%d)\n", strerror(errno), errno);
        exit (1);
      }
      printf("Disk process received %d bytes:\n", rv);
      // Interpret command
      if (rv == sizeof(internal_cmd_t)) {

        // Here interpret command
        e_cmd cmd;
        if (cmd == eExit) {
          printf("Exiting\n");
          close(comm_server[0]);
          return EXIT_SUCCESS;
        }
      }
    }

    FD_ZERO(&next_rfds); FD_ZERO(&next_wfds); FD_ZERO(&next_efds);
    FD_SET(comm_server[0], &next_rfds);

    fcntl(comm_server[0], F_SETFL, fcntl(comm_server[0], F_GETFL, 0) & ~O_NONBLOCK);

    int ofd = comm_server[0];
    next_select_width = std::max(comm_server[0],ofd) + 1;
  }
  while (true);

  close(comm_server[0]);

  return EXIT_SUCCESS;
}

答案 1 :(得分:1)

你写逻辑是有问题的,你的管道布局过于复杂。下面是您的代码,我可以根据自己的需要进行简单的调整。包含评论以帮助您。我发现在处理链式管道(这对于所有意图都是这样的)时最简单的是布置一个由显示链接的宏索引的描述符数组:

// some hand macros for access the correct pipes
#define P_READ  0
#define C_WRITE 1
#define C_READ  2
#define P_WRITE 3
#define N_PIPES 4

以上将在最终的源列表中。这些标记应该是不言而喻的,但是如果它们不是,P_XXX注意到进程使用的管道,C_XXX注意到管道进程使用。看到代码时请记住这一点:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>

// some hand macros for access the correct pipes
#define P_READ  0
#define C_WRITE 1
#define C_READ  2
#define P_WRITE 3
#define N_PIPES 4

// reads a buffer up-to len size.
ssize_t readFromPipe(int pipe, char *buff, size_t len)
{
    buff[0] = 0;
    ssize_t res = read(pipe, buff, len);
    assert(res >= 0 && "Failed to read from pipe");
    return res;
}

ssize_t writeToPipe(int pipe, const char *input)
{
    size_t len = strlen(input)+1;
    ssize_t res = write(pipe, input, len);
    assert(res == len && "Failed to write to pipe");
    return res;
}

int main(void)
{
    int pipes[N_PIPES];
    char msg[128] = {0};
    pid_t pid;

    if(pipe(pipes) < 0 || pipe(pipes+2) < 0)
    {
        printf("[ERROR] create pipes\n");
        return EXIT_FAILURE;
    }

    if((pid = fork()) < 0)
    {
        printf("[ERROR] Fork error\n");
        return EXIT_FAILURE;
    }

    // parent code
    if(pid > 0)
    {
        // Parent code. close down the child pipes; don't need them
        printf("parent(%d) create: child(%d)\n", getpid(), pid);
        close(pipes[C_WRITE]);
        close(pipes[C_READ]);

        do
        {
            if (readFromPipe(pipes[P_READ], msg, sizeof(msg)) > 0)
            {
                printf("parent(%d) read : %s\n", getpid(), msg);
                writeToPipe(pipes[P_WRITE], "-1");
            }
            else break;
        }
        while(atoi(msg) != -1);

        // close remaining pipes. no longer needed
        close(pipes[P_READ]);
        close(pipes[P_WRITE]);
    }

    else if(pid == 0)
    {
        // Child code. don't need parent write or child-read lines
        close(pipes[P_READ]);
        close(pipes[P_WRITE]);

        // write message
        writeToPipe(pipes[C_WRITE],"test message");

        // read test message
        if (readFromPipe(pipes[C_READ], msg, sizeof(msg)) > 0)
            printf("child(%d) read : %s\n", getpid(), msg);

        // write another message
        writeToPipe(pipes[C_WRITE], "-1");

        // close remaining pipes. no longer needed
        close(pipes[C_READ]);
        close(pipes[C_WRITE]);
    }

    return EXIT_SUCCESS;
}

管道描述符数组无法承受,其中最大的变化是简化的writeToPipe逻辑,它只是将终结符C字符串写入管道,包括终止空字符

ssize_t writeToPipe(int pipe, const char *input)
{
    size_t len = strlen(input)+1;
    ssize_t res = write(pipe, input, len);
    assert(res == len && "Failed to write to pipe");
    return res;
}

调用者检查结果以确保它写入了所有请求的数据,并且嵌入的assert()宏将在失败时使调试器跳闸。读取功能存在类似的逻辑。

输出(因进程ID而异)

parent(2067) create: child(2068)
parent(2067) read : test message
child(2068) read : -1
parent(2067) read : -1

我希望这会有所帮助。在处理管道时,尤其是在不太遥远的未来(see spoiler here)可能遇到的stdio重定向时,为代码提供有意义的助记符会有很大帮助,例如我使用的宏以上用于索引管道数组。

祝你好运。