2009-07-19 59 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消息隊列。