从 C 语言中的管道读取数据

Jinku Hu 2023年10月12日
  1. 使用 piperead 系统调用在 C 语言中从管道中读取数据
  2. 在 C 语言中使用 while 循环从管道中读取数据
从 C 语言中的管道读取数据

本文将演示有关如何从 C 语言中的管道读取的多种方法。

使用 piperead 系统调用在 C 语言中从管道中读取数据

管道是基于 UNIX 的系统中的进程间通信(IPC)原语的变体之一。它提供了一个单向通信通道,即两个进程之间的字节流,并且数据在一个方向上顺序移动。pipe 系统调用用于创建管道并获取其读取和写入端的文件描述符。注意,我们可以使用普通的 I/O 函数 readwrite 对管道描述符进行操作。pipe 系统调用采用包含两个元素的 int 数组,成功调用将返回两个文件描述符,分别表示第一个-读取和第二个-写入结束。请注意,写入管道的数据会在内核中进行缓冲,直到读取器检索到给定的字节为止。

在下面的示例中,我们演示了在父进程和子进程之间进行通信的管道的基本用法。首先,我们创建一个管道并将其描述符存储在 pipe_fd 数组中。接下来,我们在 switch 语句表达式中调用 fork,并在情况 0 下包含子进程的代码块。另一方面,default 情况将由父进程执行。

请注意,父级将写入从命令行参数中检索到的字符串,然后等待子级终止。同时,子进程从管道读取,并将读取的字节打印到控制台。子进程和父进程都关闭管道的一端,因为子进程继承了 fork 上父进程的文件描述符。最后,在读取发送的字节之后,子进程关闭管道的读取端,并以 exit 调用终止。

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

enum { BUF_SIZE = 4096 };

int main(int argc, char *argv[]) {
  int pipe_fd[2];
  char buf[BUF_SIZE];
  ssize_t numRead;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s string\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  if (pipe(pipe_fd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  switch (fork()) {
    case -1:
      perror("fork");
      exit(EXIT_FAILURE);

    case 0:
      if (close(pipe_fd[1]) == -1) {
        perror("close - parent");
        exit(EXIT_FAILURE);
      }

      sleep(3);
      numRead = read(pipe_fd[0], buf, BUF_SIZE);
      write(STDOUT_FILENO, buf, numRead);

      write(STDOUT_FILENO, "\n", 1);
      if (close(pipe_fd[0]) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
      }
      _exit(EXIT_SUCCESS);

    default:
      if (close(pipe_fd[0]) == -1) {
        perror("close - parent");
        exit(EXIT_FAILURE);
      }

      if (write(pipe_fd[1], argv[1], strlen(argv[1])) != strlen(argv[1])) {
        perror("write - partial/failed write");
        exit(EXIT_FAILURE);
      }

      if (close(pipe_fd[1]) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
      }

      wait(NULL);
      exit(EXIT_SUCCESS);
  }
}

在 C 语言中使用 while 循环从管道中读取数据

先前的示例代码有一个欺骗性的错误,该错误可能导致子进程中的部分读取。read 调用返回的数据可能少于管道中的数据,因此在此示例中调用一个 read 调用将是错误的。幸运的是,read 函数返回读取的字节数,我们可以实现一个循环,以用尽管道中的数据,如下一个代码示例所示。请注意,管道具有固定的容量,如果达到最大值,则写操作将阻塞,直到读取器检索到一些数据为止。

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

enum { BUF_SIZE = 4096 };

int main(int argc, char *argv[]) {
  int pipe_fd[2];
  char buf[BUF_SIZE];
  ssize_t numRead;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s string\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  if (pipe(pipe_fd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  switch (fork()) {
    case -1:
      perror("fork");
      exit(EXIT_FAILURE);

    case 0:
      if (close(pipe_fd[1]) == -1) {
        perror("close - parent");
        exit(EXIT_FAILURE);
      }

      while (1) {
        numRead = read(pipe_fd[0], buf, BUF_SIZE);
        if (numRead == -1) {
          perror("read");
          exit(EXIT_FAILURE);
        }
        if (numRead == 0) break;

        if (write(STDOUT_FILENO, buf, numRead) != numRead) {
          perror("write - partial/failed write");
          exit(EXIT_FAILURE);
        }
      }

      write(STDOUT_FILENO, "\n", 1);
      if (close(pipe_fd[0]) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
      }
      _exit(EXIT_SUCCESS);

    default:
      if (close(pipe_fd[0]) == -1) {
        perror("close - parent");
        exit(EXIT_FAILURE);
      }

      if (write(pipe_fd[1], argv[1], strlen(argv[1])) != strlen(argv[1])) {
        perror("write - partial/failed write");
        exit(EXIT_FAILURE);
      }

      if (close(pipe_fd[1]) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
      }

      wait(NULL);
      exit(EXIT_SUCCESS);
  }
}
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 创始人。Jinku 在机器人和汽车行业工作了8多年。他在自动测试、远程测试及从耐久性测试中创建报告时磨练了自己的编程技能。他拥有电气/电子工程背景,但他也扩展了自己的兴趣到嵌入式电子、嵌入式编程以及前端和后端编程。

LinkedIn Facebook