2010-08-27 40 views
1

我想使用一個socketpair有一個父進程提供輸入到一個子進程,執行一個不同的程序(例如,grep),然後讀取結果輸出。該程序掛在while循環中,該循環從子程序讀取程序的輸出。小孩將stdin和stdout放在socketpair的末尾,父母和孩子都關閉了它們未使用的一端。有趣的是,如果孩子執行我編寫的程序(好吧,我把它從Unix環境中的Stevens Advanced Programming中剝離下來),一切都按預期工作。但是,如果孩子執行grep(或其他標準程序),則父母總會在嘗試讀取輸出時掛起。我無法判斷輸入是否達不到grep,或者如果grep無法確定輸入的結束或輸出是否以某種方式丟失。使用socketpair雙向通信:掛起從子進程讀取輸出

下面的代碼:

#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/wait.h> 
#include <unistd.h> 
#include <signal.h> 
#include <cstdio> 
#include <cerrno> 
#include <iostream> 
using namespace std; 

void 
sigpipe_handler(int sig, siginfo_t *siginfo, void * context) { 
    cout << "caught SIGPIPE\n"; 
    pid_t pid; 

    if (errno == EPIPE) { 
    throw "SIGPIPE caught"; 
    } 
} 

int main(int argc, char** argv) { 

    struct sigaction sa; 
    memset(&sa, '\0', sizeof(struct sigaction)); 
    sa.sa_sigaction = sigpipe_handler; 
    sa.sa_flags = SA_SIGINFO | SA_RESTART; 
    sigaction(SIGPIPE, &sa, NULL); 

    int sp[2]; 
    socketpair(PF_UNIX, SOCK_STREAM, AF_UNIX, sp); 

    pid_t childPid = fork(); 

    if (childPid == 0) { 
    close(sp[0]); 
    if (dup2(sp[1], STDIN_FILENO) != STDIN_FILENO) throw "dup2 error to stdin"; 
    if (dup2(sp[1], STDOUT_FILENO) != STDOUT_FILENO) throw "dup2 error to stdout"; 

    execl("/bin/grep", "grep", "-n", "namespace", (char*)NULL); 
    } else { 
    close(sp[1]); 
    char line[80]; 
    int n; 
    try { 
     while (fgets(line, 80, stdin) != NULL) { 
n = strlen(line); 
if (write(sp[0], line, n) != n) { 
    throw "write error to pipe"; 
} 

if ((n=read(sp[0], line, 80)) < 0) { // hangs here 
    throw "read error from pipe"; 
} 
if (n ==0) { 
    throw "child closed pipe"; 
    break; 
} 
line[n] = 0; 
if (fputs(line, stdout) == EOF) { 
    throw "puts error"; 
} 
if (ferror(stdin)) { 
    throw "fgets error on stdin"; 
} 
exit(0); 
     } 
    } catch (const char* e) { 
     cout << e << endl; 
    } 

    int status; 
    waitpid(childPid, &status, 0); 
    } 
} 

回答

3

您的代碼掛起,因爲grep的輸出可能少於80個字節,並且您正在sp [0]上發出阻塞讀取。這樣做的正確方法是將兩個套接字標記爲非阻塞,並選擇()兩者。

您在等待()之前忘記關閉(sp [0]),這會讓您的子進程等待輸入。

+0

有趣。通過使用strace,我發現grep在接收到我父進程必須提供的所有輸入(使用sscanf工作正常的子進程)後正在等待輸入。我嘗試在sp [0]上使用shutdown(用於讀取),但它似乎不起作用。我現在將嘗試使用關閉。 我想我假設套接字默認是非阻塞的 - 我一定會嘗試明確地設置套接字爲非阻塞並使用select。 謝謝! – roadrider 2010-08-31 21:44:42

0

它正常工作與貓,所以這個問題是使用grep。可能是grep輸出在連接到除終端之外的其他東西時行爲不同。或者由於某種原因沒有檢測到模式。

3

由於您無法控制子進程中的緩衝,所以無法使用UNIX管道或套接字對與子進程實現無死鎖雙向通信。

恰恰是這樣,cat可以被信任讀取一行並立即打印它,無論其標準輸出是tty,管道還是套接字。對於grep(實際上大多數使用stdio的程序),情況並非如此,這將緩衝輸出進程中(在stdio緩衝區中),並推遲調用write()調用,直到緩衝區已滿或stdio流關閉(通常是因爲grep在輸入EOF後即將退出)。

您可以通過使用pseudo-tty代替面向線程序的程序(包括grep)而不使用緩衝區;看看libexpect(3)。但是在一般情況下,您將不得不爲每條消息重新運行不同的子進程,從而允許使用EOF來指示每條消息的結束並導致命令(或命令流水線)中的任何緩衝區被刷新。

查看關於perlipc手冊頁中關於此問題的更多信息(它適用於Perl中的雙向管道,但無論用於主程序的語言如何,緩衝注意事項均適用)。