2010-08-27 3 views
1

J'essaie d'utiliser un socketpair pour qu'un processus parent fournisse une entrée à un processus enfant qui exécute un programme différent (par exemple, grep) et ensuite lire le résultat sortie. Le programme se bloque dans la boucle while qui lit la sortie du programme que l'enfant exécute. L'enfant dupe stdin et stdout à son extrémité de la socketpair et le parent et l'enfant ferment leur extrémité inutilisée de la paire. Il est intéressant de noter que si l'enfant exécute un programme que j'ai écrit (OK, je l'ai arraché à Stevens Advanced Programming dans l'environnement Unix), tout fonctionne comme prévu. Cependant, si l'exécutable exécute grep (ou un autre programme standard), le parent se bloque invariablement en essayant de lire la sortie. Je ne peux pas dire si l'entrée n'atteint pas grep ou si le grep ne peut pas déterminer la fin de l'entrée ou si la sortie est en quelque sorte perdue.communication bidirectionnelle using socketpair: bloque la sortie de lecture du processus enfant

Voici le code:

#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); 
    } 
} 

Répondre

3

Votre code se bloque car la sortie de grep peut être inférieure à 80 octets et vous lancez une lecture bloquante sur sp [0]. La bonne façon de procéder est de marquer les deux sockets comme non bloquantes et de les sélectionner() sur les deux.

Vous avez également oublié de fermer (sp [0]) avant d'attendre(), ce qui laissera votre processus enfant en attente de saisie.

+0

Intéressant. En utilisant strace, j'ai trouvé que grep attend une entrée après avoir reçu toutes les entrées que mon processus parent a dû donner (un processus fils qui a utilisé sscanf a bien fonctionné). J'ai essayé d'utiliser shutdown (pour lire) sur sp [0] mais ça n'a pas l'air de marcher. Je vais maintenant essayer d'utiliser close à la place. Je pense avoir supposé que les sockets n'étaient pas bloquants par défaut - j'essaierai certainement de paramétrer explicitement les sockets sur non-bloquants et en utilisant select. Merci! – roadrider

0

Il fonctionne très bien avec un chat, de sorte que le problème est avec grep. Peut être la sortie grep se comporte différemment lorsqu'il est connecté à autre chose qu'un terminal. Ou il ne détecte pas le motif pour une raison quelconque.

3

Vous ne pouvez pas obtenir une communication bidirectionnelle sans interblocage avec un sous-processus utilisant des tuyaux UNIX ou des paires de connecteurs UNIX, car vous ne pouvez pas contrôler la mise en mémoire tampon dans le sous-processus.

Il se trouve juste que cat peut être fiable pour lire une ligne et l'imprimer immédiatement, que sa sortie standard soit un tty, un pipe ou un socket. Ce n'est pas le cas avec grep (et en fait la plupart des programmes utilisant stdio), qui tamponnera la sortie in-process (dans les tampons stdio) et différera l'appel write() jusqu'à ce que le tampon soit plein ou le flux stdio fermé (typiquement parce que grep est sur le point de sortir après avoir vu EOF en entrée).

Vous pouvez faire en sorte que les programmes orientés ligne (y compris grep) ne soient pas mis en mémoire tampon en utilisant un pseudo-tty à la place; Jetez un oeil à libexpect(3). Mais dans le cas général, vous devrez réexécuter un sous-processus différent pour chaque message, ce qui permet d'utiliser EOF pour signaler la fin de chaque message et provoquer le vidage de tous les tampons de la commande (ou du pipeline de commandes).

Pour plus d'informations sur ce problème, reportez-vous à la page de manuel perlipc (pour les tuyaux bidirectionnels en Perl mais les considérations de mise en mémoire tampon s'appliquent quelle que soit la langue utilisée pour le programme principal).

Questions connexes