UNIX / Linux IPC:从管道读取。如何在运行时知道数据长度?

时间:2009-07-19 22:16:34

标签: linux unix ipc pipe

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

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

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

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

共享内存会更快吗?

谢谢, -Dhruv

6 个答案:

答案 0 :(得分:5)

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

从管道(7)手册页:

  

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

以下示例取自pipe(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并进行解析。重复unil read返回0,表示子项已关闭管道。

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

答案 1 :(得分:2)

由于写入结束总是可以向管道写入更多数据,因此无法知道其中的数据大小。您可以让发件人先写入长度,或者您可以分配一个较大的缓冲区,尽可能多地读取,然后调整缓冲区大小,如果它不够大。

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

答案 2 :(得分:2)

您无法从管道获取任何大小信息,因为没有大小。

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

换句话说,在子节点中,输出即将到来的输出的大小为int,然后写出实际输出;你在父母中读取了大小(它是一个int,所以它总是大小相同),然后读取那么多字节。

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

答案 3 :(得分:1)

为什么不将长度写入管道中(例如)第一个'n'个字节?然后在另一端你可以读取这些字节,确定长度,然后读取该字节数(即你有一个非常简单的协议)

答案 4 :(得分:0)

其他海报是正确的:你必须有办法自己指定数据包的长度。一个具体,实用的方法是使用netstrings。创建和解析很简单,并且它由一些常见框架支持,例如Twisted

答案 5 :(得分:0)

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