使用socketpair进行双向通信:挂起从子进程读取输出

时间:2010-08-27 15:06:56

标签: c++ linux sockets ipc

我正在尝试使用socketpair让父进程向子进程提供输入,该进程执行不同的程序(例如,grep),然后读取结果输出。该程序在while循环中挂起,该循环读取子程序执行的程序的输出。子程序将stdin和stdout连接到socketpair的末尾,父项和子项都关闭它们未使用的结尾。

有趣的是,如果孩子执行我编写的程序(好的,我在Unix环境中将其从Stevens高级编程中删除),一切都按预期工作。但是,如果孩子执行grep(或其他一些标准程序),父母总是在试图读取输出时挂起。我无法判断输入是否未达到grep,或者grep是否无法确定输入结束或输出是否以某种方式丢失。

以下是代码:

#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 个答案:

答案 0 :(得分:3)

您的代码挂起,因为grep的输出可能少于80个字节,并且您在sp [0]上发出阻塞读取。这样做的正确方法是将两个套接字标记为非阻塞,并在两个套接字上选择()。

在wait()之前你也忘了关闭(sp [0]),这会让你的子进程等待输入。

答案 1 :(得分:3)

您无法使用UNIX管道或套接字对子进程实现无死锁双向通信,因为您无法控制子进程中的缓冲。

恰好可以信任cat读取一行并立即打印它,无论其标准输出是tty,管道还是套接字。这不是grep(实际上大多数使用stdio的程序)的情况,它将缓冲进程中的输出(在stdio缓冲区中)并推迟write()调用,直到缓冲区已满或者stdio流已关闭(通常因为grep即将在输入时看到EOF后退出。)

你可以通过使用伪tty来欺骗面向行的程序(包括grep)而不是缓冲;看看libexpect(3)。但是在一般情况下,您必须为每条消息重新运行不同的子进程,这允许使用EOF来指示每条消息的结束并导致命令(或命令管道)中的任何缓冲区被刷新。 / p>

perlipc手册页中查看有关此问题的更多信息(它适用于Perl中的双向管道,但无论主程序使用何种语言,缓冲注意事项都适用。)

答案 2 :(得分:0)

它适用于cat,所以问题在于grep。当连接到终端以外的其他地方时,grep输出可能表现不同。或者由于某种原因它没有检测到模式。