2009-07-19 55 views
4

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

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

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

理查德史蒂文斯在UNIX环境下的高级编程中的所有例子都指定了写入管道时的数据长度或者依赖于fgets()stdio.h函数。由于我关心速度,我想尽可能避免使用stdio.h。

这对于共享内存来说会更快吗?

感谢, -Dhruv

+0

谢谢大家的回复。 通过管道发送数据的子进程基本上是一个工具的输出,其中列出了有关系统的一些统计信息。每次输出的长度都不相同。我已经将STDOUT复制到子节点的管道写入端。 我的理解是,执行该工具后的子进程会自动将输出放在管道的写入端,因为我已经复制了STDOUT。一旦我完成在父项中的等待,我应该可以使用read()从管道中读取数据。如何在父级获取read()的长度? – Dhruv 2009-07-20 00:42:32

+0

我将对收集的输出进行一些解析。由于工具的输出格式非常好,并且有换行符和空格,所以我可以通过使用指针遍历缓冲区,而不是将中间数据存储在磁盘上的文件中,然后使用string.h函数(如sscanf())来更轻松高效地完成此操作。 我想知道如果在管道上做连续的lseek()将有任何帮助来获取数据的大小。 – Dhruv 2009-07-20 00:55:18

+0

请阅读我的答案更新。它会告诉你如何在数据可用时读取数据。 – Inshallah 2009-07-20 01:39:49

回答

5

因为它似乎是你打算让从管道中的所有数据的单一读出,我觉得有以下将为你比分隔符+编码或在其他的答案建议miniheader技术更好:

从管(7)用户手册:

如果所有文件描述符参照 管的写入结束一直 封闭,然后尝试读取(2) 从管将看到结束文件 (读(2)将返回0)。

以下示例是从管道(2)手册页取得的,并且已颠倒过来,以便孩子进行写作,父母进行阅读(只是可以肯定)。我还添加了一个可变大小的缓冲区。孩子会睡5秒钟。延迟将确保孩子的出口()与小孩无关(在孩子退出之前,父母将打印完整的一行)。

#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读取返回0,这表明孩子已关闭管道。

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

1

为什么不写长到管道为(比方说)第 'N' 个字节?然后在另一端读取这些字节,确定长度,然后读取该字节数(即,您有一个非常简单的协议)

2

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

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

2

由于没有尺寸,您无法从管道获取任何尺寸信息。

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

换句话说,在孩子中,输出即将到来的输出的大小为int,然后写出实际的输出;在父母你读的大小(它是一个int,所以它总是相同的大小),然后读取很多字节。或者:定义一个结束字符,直到你看到它,假设你需要继续阅读。然而,这可能需要某种转义/编码机制,并且可能不会那么快。我认为这基本上是fgets所做的。

0

其他海报是正确的:你必须有一种方法来指定你自己的包长度。一个具体而实际的方法是使用netstrings。创建和解析起来非常简单,并且支持一些常见的框架,如Twisted

0

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