簡體   English   中英

GNU C多管道處理

[英]GNU C Multi-process handling with pipes

我在大學里被教給我一個在后台處理進程的最簡單方法,即通過管道read()函數掛起子進程/父進程的運行。 老實說,我已經在做作業兩周了,無法解決異步流程處理問題。 我將代碼最小化,以編寫帶有2個管道的單個子處理,並使用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;
}

錯誤如下:

[PARENT] Create child1
TEXT: TEST MESSAGE
[PARENT] pipe message: TEST MESSAGE
TEXT: -1
[CHILD1] pipe message: -1
TEXT: -1�po
[PARENT] pipe message: -1�T MESSAGE
TEXT: -1�po

我試圖用信號實現此過程處理,但是在最終應用程序中,我將需要3個不同的子進程,並且異步運行會導致信號處理問題。 我還嘗試在網上找到一個教程,但是每個多處理主題都涵蓋了一個簡單的單消息解決方案,從父級到子級,反之亦然。 但是我在父進程中需要一個基於字符的菜單,因此子進程應連續等待父信號/消息,並且父進程也需要等待子進程完成實際任務。 請幫幫我,因為我真的很困。 如果您對流程處理有任何正常的解決方案,請告訴我,因為我知道這段代碼很糟糕。 唯一的原因是缺少正確的教程。 提前致謝。

好的,我有一個模板,可以用於嵌入式解決方案。 使用此方法,我創建了一個有效的解決方案,其中包含客戶端循環,但僅聲明了服務器循環。 應該清楚如何解決。

應該清楚如何將多個讀取器/寫入器添加到select調用中。 注意有使用SOCK_DGRAM。 除了套接字對之外,還可以添加管道(這對於在流程之間流傳輸原始數據是首選的)。 我希望您可以從中學到一些東西。 我們應該刪除它(有時)。 它需要完成一些工作(服務器循環)

#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;
}

您的編寫邏輯值得懷疑,管道的布局也很復雜。 下面是根據我的需求量身定制的簡單代碼。 包含評論以幫助您。 我發現在處理鏈管(最適合所有意圖)時,最簡單的方法是布置一個由表現鏈的宏索引的描述符數組:

// 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字符串寫入管道, 包括終止的null char

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

我希望這有幫助。 當處理管道時,尤其是在不久的將來可能會遇到的stdio重定向( 請參閱此處的破壞器 )時,為您的代碼提供有意義的助記符(例如我在上面使用的宏)將極大地幫助您索引管道數組。

祝你好運。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM