【问题标题】:UNIX/Linux IPC : Reading from a pipe. How to know length of data at runtime?UNIX/Linux IPC:从管道读取。如何在运行时知道数据的长度?
【发布时间】:2010-11-12 04:04:16
【问题描述】:

我有一个子进程,它生成一些可变长度的输出,然后使用半双工管道将其发送给父进程。在父级中,如何使用 read() 函数? 由于数据每次可以具有不同的长度,我如何在运行时知道数据的大小以对缓冲区执行任何 malloc() ?可以在管道文件描述符上使用 fstat() 函数吗?

我知道 read() 函数将读取指定数量的字节,但如果在读取请求的字节之前到达文件末尾(不是 EOF 字符),它将返回 0。

我专门运行带有 2.6.27-9 内核的 Ubuntu GNU/Linux。

Richard Stevens 的“UNIX 环境中的高级编程”中的所有示例都指定了写入管道时的数据长度,或者依赖于 fgets() stdio.h 函数。由于我关心速度,我想尽可能地远离使用 stdio.h。

共享内存一定会更快吗?

谢谢, -Dhruv

【问题讨论】:

  • 感谢大家的回复。通过管道发送数据的子进程基本上是一个工具的执行输出,它列出了一些关于系统的统计信息。每次输出的长度都不一样。我已将 STDOUT 复制到子管道的写入端。我的理解是,执行该工具后的子进程会自动将输出放在管道的写入端,因为我已经复制了 STDOUT。一旦我在父级中完成等待,我应该能够使用 read() 从管道中读取数据。如何获取父级中 read() 的长度?
  • 我将对收集的输出进行一些解析。由于该工具的输出使用换行符和空格进行了很好的格式化,因此我可以通过使用指针遍历缓冲区而不是将中间数据存储在磁盘上的文件中然后使用 string.h 函数(如 sscanf())来更轻松有效地完成此操作。我想知道在管道上连续执行 lseek() 是否有助于获取数据的大小。
  • 请阅读我的答案更新。如果您想在数据可用时读取数据,它将告诉您该怎么做。
  • 您不能在管道上使用 lseek()。尽管它有一个文件描述符并且行为有点像文件,但管道不是文件,而不是套接字。

标签: linux unix ipc pipe


【解决方案1】:

由于您似乎打算对管道中的所有数据进行一次读取,我认为以下内容将比其他答案中建议的分隔符+编码或 miniheader 技术更好地为您服务:

来自管道 (7) 手册页:

如果所有文件描述符引用 管道的写端已经 关闭,然后尝试读取(2) 从管道将看到文件结尾 (read(2) 将返回 0)。

以下示例取自管道 (2) 联机帮助页并颠倒过来,以便子代写,父代读(只是为了确定)。我还添加了一个可变大小的缓冲区。孩子将睡 5 秒钟。延迟会保证子进程的exit()可以和pipeio无关(父进程会在子进程退出前打印完整的一行)。

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

char *
slurpfd(int fd)
{
    const int bytes_at_a_time = 2;
    char *read_buffer = NULL;
    int buffer_size = 0;
    int buffer_offset = 0;
    int chars_io;
    while (1) {
      if (buffer_offset + bytes_at_a_time > buffer_size) {
        buffer_size = bytes_at_a_time + buffer_size * 2;
        read_buffer = realloc(read_buffer, buffer_size);
        if (!read_buffer) {
          perror("memory");
          exit(EXIT_FAILURE);
        }
      }

      chars_io = read(fd,
                  read_buffer + buffer_offset,
                  bytes_at_a_time);
      if (chars_io <= 0) break;
      buffer_offset += chars_io;
    }

    if (chars_io < 0) {
      perror("read");
      exit(EXIT_FAILURE);
    }

    return read_buffer; /* caller gets to free it */
}

int
main(int argc, char *argv[])
{
  int pipefd[2];
  pid_t cpid;

  assert(argc == 2);

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

  cpid = fork();
  if (cpid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (cpid == 0) {     /* Child writes argv[1] to pipe */
    close(pipefd[0]);  /* Close unused read end */

    write(pipefd[1], argv[1], strlen(argv[1]) + 1);

    close(pipefd[1]);  /* Reader will see EOF */
    /* sleep before exit to make sure that there
       will be a delay after the parent prints it's
       output */
    sleep(5);
    exit(EXIT_SUCCESS);
  } else {             /* Parent reads from pipe */
    close(pipefd[1]);  /* Close unused write end */

    puts(slurpfd(pipefd[0]));

    close(pipefd[0]);
    wait(NULL);        /* Wait for child */
    _exit(EXIT_SUCCESS);
  }
}

从您的评论中,我现在看到您可能希望在数据可用时读取数据,以更新 UI 或其他任何内容,以反映您的系统状态。为此,请以非阻塞 (O_NONBLOCK) 模式打开管道。重复阅读任何可用的内容,直到返回 -1 并且 errno == EAGAIN 并进行解析。重复直到读取返回0,这表明孩子已经关闭了管道。

要为 File* 函数使用内存缓冲区,您可以使用 GNU C 库中的 fmemopen()。

【讨论】:

  • 在等待子进程完成数据写入后,我使用 mmap() 打开了转储的数据文件,并使用 fstat() 查找文件大小。解析很容易,因为我知道转储文件的格式。
  • 谢谢;您的示例代码几乎对我有用,只是它无法终止 read_buffer!
  • 是的,这是真的。对 puts(slurpfd(...)) 的调用将无法正常工作,因为缓冲区不是空/零终止的。感谢您指出这一点。
【解决方案2】:

如果您的消息不是太大,您可以尝试使用 IPC 消息队列。

【讨论】:

    【解决方案3】:

    其他海报是正确的:您必须有一种方法自己指定数据包的长度。一种具体实用的方法是使用netstrings。它的创建和解析都很简单,并且得到了Twisted等一些常见框架的支持。

    【讨论】:

      【解决方案4】:

      您无法从管道中获取任何尺寸信息,因为没有尺寸。

      您需要使用定义的大小或分隔符。

      也就是说,在child中,将即将输出的大小输出为int,然后写出实际的输出;在你读取大小的父级中(它是一个 int,所以它总是相同的大小),然后读取那么多字节。

      或者:定义一个结束字符,直到你看到它,假设你需要继续阅读。然而,这可能需要某种转义/编码机制,并且可能不会那么快。我认为这基本上是 fgets 所做的。

      【讨论】:

        【解决方案5】:

        由于写入端总是可以向管道写入更多数据,因此无法知道管道中数据的大小。您可以让发送方先写入长度,也可以分配一个较大的缓冲区,尽可能多地读取,如果缓冲区不够大,则调整其大小。

        共享内存会更快,因为它可以避免复制并且可能会避免一些系统调用,但是跨 shmem 传输数据所需的锁定协议更复杂并且容易出错,因此除非绝对需要,否则通常最好避免使用共享内存。此外,对于共享内存,您必须在分配缓冲区时为要传输的数据设置一个固定的最大大小。

        【讨论】:

          【解决方案6】:

          为什么不将长度作为(比如说)前“n”个字节写入管道?然后在另一端你可以读取这些字节,确定长度,然后读取那个字节数(即你有一个非常简单的协议)

          【讨论】:

          • 框架可能会比让你的管道破裂更痛苦。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-07
          相关资源
          最近更新 更多