двунаправленная связь с использованием socketpair: зависает результат чтения дочернего процесса - PullRequest
1 голос
/ 27 августа 2010

Я пытаюсь использовать сокет-пару, чтобы родительский процесс предоставлял входные данные дочернему процессу, который запускает другую программу (например, grep), а затем читал полученный результат. Программа зависает в цикле while, который читает выходные данные программы, которую исполняет дочерний элемент. Дочерний элемент дублирует stdin и stdout на свой конец пары сокетов, а родительский и дочерний элементы закрывают неиспользованный конец пары.

Интересно, что если ребенок исполняет программу, которую я написал (хорошо, я оторвал ее от Расширенного программирования Стивенса в среде Unix), все работает как положено. Однако, если дочерний файл исполняет 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 ]

3 голосов
/ 29 августа 2010

Ваш код зависает, поскольку вывод grep может быть менее 80 байт, и вы выполняете блокирующее чтение на sp [0]. Правильный способ сделать это - пометить оба сокета как неблокирующие и выбрать () поверх них обоих.

Вы также забыли закрыть (sp [0]) перед ожиданием (), что оставит ваш дочерний процесс в ожидании ввода.

3 голосов
/ 25 апреля 2012

Невозможно добиться двунаправленной связи без взаимоблокировок с подпроцессом с использованием каналов или пар сокетов UNIX, потому что вы не можете контролировать буферизацию в подпроцессе.

Просто так получается, что cat можно доверятьпрочитать одну строку и сразу же напечатать ее, независимо от того, является ли ее стандартный вывод tty, pipe или сокетом.Это не относится к grep (и фактически к большинству программ, использующих stdio), которые будут буферизовать вывод в процессе (в буферах stdio) и откладывать вызов write() до тех пор, пока не будет заполнен буфер или поток stdio не будет закрыт.(обычно потому, что grep собирается завершить работу после просмотра EOF при вводе).

Вы можете обмануть ориентированные на строки программы (включая grep), чтобы они не буферизовались, используя вместо этого псевдо-tty;взгляните на libexpect(3).Но в общем случае вам придется повторно запускать разные подпроцессы для каждого сообщения, что позволяет использовать EOF для оповещения об окончании каждого сообщения и вызывать сброс любых буферов в команде (или конвейере команд).

Подробнее об этой проблеме см. На справочной странице perlipc (она предназначена для двунаправленных каналов в Perl, но соображения буферизации применяются независимо от языка, используемого для основной программы).

0 голосов
/ 27 августа 2010

Отлично работает с cat, поэтому проблема с grep.Может быть, вывод grep ведет себя иначе, когда подключен к чему-то другому, чем к терминалу.Или это не обнаружение шаблона по какой-то причине.

...