Uso de la función de sondeo con transmisiones en búfer

Estoy tratando de implementar un tipo de sistema de comunicación cliente-servidor utilizando la función de sondeo en C. El flujo es el siguiente:

  1. El progtwig principal abre un subproceso
  2. El proceso hijo llama a la función exec para ejecutar some_binary
  3. Padres e hijos se envían mensajes entre ellos alternativamente, cada mensaje que se envía depende del último mensaje recibido.

Intenté implementar esta poll usando poll , pero encontré problemas porque el proceso secundario almacena temporalmente su salida, lo que hace que mis llamadas de poll expiren. Aquí está mi código:

 int main() { char *buffer = (char *) malloc(1000); int n; pid_t pid; /* pid of child process */ int rpipe[2]; /* pipe used to read from child process */ int wpipe[2]; /* pipe used to write to child process */ pipe(rpipe); pipe(wpipe); pid = fork(); if (pid == (pid_t) 0) { /* child */ dup2(wpipe[0], STDIN_FILENO); dup2(rpipe[1], STDOUT_FILENO); close(wpipe[0]); close(rpipe[0]); close(wpipe[1]); close(rpipe[1]); if (execl("./server", "./server", (char *) NULL) == -1) { fprintf(stderr, "exec failed\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } else { /* parent */ /* close the other ends */ close(wpipe[0]); close(rpipe[1]); /* poll to check if write is good to go This poll succeeds, write goes through */ struct pollfd pfds[1]; pfds[0].fd = wpipe[1]; pfds[0].events = POLLIN | POLLOUT; int pres = poll(pfds, (nfds_t) 1, 1000); if (pres > 0) { if (pfds[0].revents & POLLOUT) { printf("Writing data...\n"); write(wpipe[1], "hello\n", 6); } } /* poll to check if there's something to read. This poll times out because the child buffers its stdout stream. */ pfds[0].fd = rpipe[0]; pfds[0].events = POLLIN | POLLOUT; pres = poll(pfds, (nfds_t) 1, 1000); if (pres > 0) { if (pfds[0].revents & POLLIN) { printf("Reading data...\n"); int n = read(rpipe[0], buffer, 1000); buffer[n] = '\0'; printf("child says:\n%s\n", buffer); } } kill(pid, SIGTERM); return EXIT_SUCCESS; } } 

El código del servidor es simplemente:

 int main() { char *buffer = (char *) malloc(1000); while (scanf("%s", buffer) != EOF) { printf("I received %s\n", buffer); } return 0; } 

¿Cómo evito que las llamadas de poll caduquen debido al almacenamiento en búfer?

EDITAR:

Me gustaría que el progtwig funcione incluso cuando el binario exec sea ​​externo, es decir, no tengo control sobre el código, como un comando de unix, por ejemplo, cat o ls .

Necesitas, como respondí en una respuesta relacionada a una pregunta anterior por tu parte, implementar un ciclo de eventos ; como su nombre lo indica, es un bucle , por lo que debe codificar en el proceso principal:

 while (1) { // simplistic event loop! int status=0; if (waitpid(pid, &status, WNOHANG) == pid) { // clean up, child process has ended handle_process_end(status); break; }; struct pollpfd pfd[2]; memset (&pfd, 0, sizeof(pfd)); // probably useless but dont harm pfd[0].fd = rpipe[0]; pfd[0].events = POLL_IN; pfd[1].fd = wpipe[1]; pfd[0].event = POLL_OUT; #define DELAY 5000 /* 5 seconds */ if (poll(pfd, 2, DELAY)>0) { if (pfd[0].revents & POLL_IN) { /* read something from rpipe[0]; detect end of file; you probably need to do some buffering, because you may eg read some partial line chunk written by the child, and you could only handle full lines. */ }; if (pfd[1].revents & POLL_OUT) { /* write something on wpipe[1] */ }; } fflush(NULL); } /* end while(1) */ 

no se puede predecir en qué orden las tuberías son legibles o escribibles, y esto puede suceder muchas veces. Por supuesto, está involucrado un montón de almacenamiento en búfer (en el proceso principal), le dejo los detalles … No tiene influencia sobre el almacenamiento en búfer en el proceso secundario (algunos progtwigs detectan que su salida es o no un terminal con isatty ).

Lo que le da un ciclo de sondeo de eventos como el anterior es evitar la situación de locking donde el proceso hijo está bloqueado porque su tubería estándar está llena, mientras que el padre está bloqueado escribiendo (a la tubería stdin del niño) porque la tubería está llena: con un evento repetición, lee en cuanto se sondean algunos datos legibles en el conducto de entrada (es decir, la salida estándar del proceso secundario), y se escriben algunos datos tan pronto como la tubería de salida se pueda escribir (es decir, no esté llena). No se puede predecir con anticipación en qué orden se producen estos eventos “la salida del hijo es legible por el padre” y “la entrada del hijo es modificable por el padre”.

¡Recomiendo leer la Progtwigción Avanzada de Linux que tiene varios capítulos que explican estos problemas!

Por cierto, mi ciclo simplificado de eventos es un poco erróneo: si el proceso secundario finaliza y algunos datos permanecen en su canal estándar, su lectura no está completa. Podría mover la prueba de waitpid después de la poll

Además, no espere que una sola write (desde el proceso secundario) en una tubería desencadene una sola read en el proceso principal. En otras palabras, no hay noción de la longitud del mensaje. Sin embargo, POSIX sabe acerca de PIPE_MAX …. Consulte su documentación de escritura . Probablemente su memoria intermedia pasada para read y write debe ser de tamaño PIPE_MAX .

Repito: necesitas llamar al poll dentro de tu ciclo de eventos y muy probablemente se llamará varias veces (¡porque tu ciclo se repetirá muchas veces!) E informará que los extremos de las tuberías legibles o escribibles son impredecibles (y no reproducibles) ¡orden! Una primera ejecución de tu progtwig podría informar ” rpipe[0] legible”, read 324 bytes desde allí, repites el bucle de evento, poll dice ” wpipe[1] escribible”, puedes write 10 bytes en él, repites el bucle de evento, la poll dice que ” rpipe[0] legible”, read 110 bytes de él, repites el bucle de evento, poll repite ” rpipe[0] legible”, read 4096 bytes de él, etc. etc. .. Una segunda ejecución del mismo progtwig en el mismo entorno daría diferentes eventos, como: la poll dice que ” wpipe[1] escribible”, usted write 1000 bytes, se repite el ciclo, la poll dice que ” rpipe[0] legible, etc.

NB: su problema no es el almacenamiento en búfer en el progtwig secundario (“cliente”), que asumimos que no puede cambiar. Entonces, lo que importa no son los datos almacenados, sino la entrada y salida genuina (que es lo único que puede observar el proceso principal: el almacenamiento intermedio secundario interno es irrelevante para el padre), es decir, los datos que su progtwig hijo ha podido realmente leer (2) y escribir (2) . Y si va a través de una tubería (7) , dichos datos se convertirán en encuesta (2) -hablables en el proceso principal (y el proceso principal puede read o write algo después de POLL_IN o POLL_OUT en el campo de revents actualizado después de la poll ). Por cierto, si codificaste al niño, no olvides llamar a fflush en los lugares apropiados dentro de él.

Parece que hay dos problemas en tu código. “stdout” está guardado de forma predeterminada, por lo que el servidor debe descargarlo explícitamente:

 printf("I received %s\n", buffer); fflush(stdout); 

Y el progtwig principal no debe registrarse para POLLOUT cuando intenta leer (pero es posible que desee registrarse en POLLERR ):

 pfds[0].fd = rpipe[0]; pfds[0].events = POLLIN | POLLERR; 

Con estas modificaciones obtienes el resultado esperado:

 $ ./main
 Escribir datos ...
 Leyendo datos ...
 niño dice:
 Recibí hola

En general, también debe verificar el valor de retorno de poll() y repetir la llamada si es necesario (por ejemplo, en el caso de una llamada al sistema interrumpida o un tiempo de espera excedido).