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

Posted

技术标签:

【中文标题】使用 socketpair 进行双向通信:挂起子进程的读取输出【英文标题】:bi-directional communication using socketpair: hangs reading output from child process 【发布时间】:2010-08-27 15:06:56 【问题描述】:

我正在尝试使用套接字对让父进程向执行不同程序(例如 grep)的子进程提供输入,然后读取结果输出。该程序挂在 while 循环中,该循环从子程序执行的程序中读取输出。子程序将 stdin 和 stdout 复制到套接字对的末尾,并且父子程序都关闭了未使用的对子程序的末尾。

有趣的是,如果孩子执行我编写的程序(好吧,我从 Stevens Advanced Programming in the Unix Env​​ironment 中抄袭了它),一切都会按预期工作。但是,如果孩子执行 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);
  

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

有趣。通过使用 strace 我发现 grep 在接收到我的父进程必须提供的所有输入后正在等待输入(使用 sscanf 的子进程工作正常)。我尝试在 sp[0] 上使用关闭(用于读取),但它似乎不起作用。我现在将尝试使用 close 代替。我想我假设默认情况下套接字是非阻塞的 - 我肯定会尝试将套接字显式设置为非阻塞并使用 select。谢谢!【参考方案2】:

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

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

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

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

【讨论】:

【参考方案3】:

它在 cat 上运行良好,所以问题出在 grep 上。当连接到终端以外的其他东西时,grep 输出的行为可能会有所不同。或者由于某种原因它没有检测到模式。

【讨论】:

以上是关于使用 socketpair 进行双向通信:挂起子进程的读取输出的主要内容,如果未能解决你的问题,请参考以下文章

socketpair实现进程通信

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

Android Framework实战开发-socketpair介绍及它在android系统源码使用分析

10.3android输入系统_必备Linux编程知识_任意进程双向通信(scoketpair+binder)

高级I/O-----socketpair