Tubos Múltiples en C

Waqar Aslam 12 octubre 2023
  1. Descripción general de las tuberías en C
  2. Implementar múltiples tuberías en C
Tubos Múltiples en C

Este artículo demuestra la implementación de múltiples tuberías en C.

Descripción general de las tuberías en C

Una tubería es una representación abstracta de un enlace entre dos procesos diferentes en los que la salida estándar devuelta por un proceso se convierte en la entrada estándar del otro. Las canalizaciones son un componente importante del sistema operativo UNIX que permite que los procesos vinculados se comuniquen entre sí (comunicación entre procesos).

Las tuberías solo pueden comunicarse en una dirección; por lo tanto, podemos utilizarlos para que un proceso escriba en la tubería mientras que otro lee de la tubería. Los conductos son áreas de la memoria principal que se manejan de la misma manera que los archivos virtuales, y esta acción abre uno.

Implementar múltiples tuberías en C

Veamos un ejemplo con 1 proceso padre y 2 procesos hijo. Enviamos un valor garantizado para estar completo desde el proceso principal al proceso secundario a través de canalizaciones, y luego enviamos ese valor de regreso al proceso principal con algún incremento agregado.

Para comenzar, necesitaremos importar las bibliotecas para acceder a las clases y funciones incluidas en nuestra aplicación. Además, se debe desarrollar una función principal().

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[]) {}

Dentro de la función main, generaremos descriptores de archivo para tres conductos para que podamos leer y escribir en otros procesos. Luego, deberá definir una variable con el tipo i para que sea un número entero para establecer un bucle for que generará tres tuberías y verificar que el valor de todas las tuberías sea menor que cero para devolver un error.

int fd[3][2];
int i;
for (i = 0; i < 3; i++) {
  if (pipe(fd[i]) < 0) {
    return 1;
  }
}

Una vez que hayamos terminado de abrir las tuberías, el siguiente paso será comenzar a bifurcar cada proceso. Como resultado, debe establecer una variable llamada processIdOne y luego asignar la función fork().

Además, aplique una verificación para verificar que el valor de processIdOne no sea menor que cero.

int processIdOne = fork();
if (processIdOne < 0) {
  return 2;
}

Ahora, comprobamos si el valor de processIdOne es igual a cero; si es así, esto indica que estamos en el primer proceso secundario. La función cerrar () debe usarse dentro del primer proceso secundario para cerrar todos los descriptores de archivo excepto el que pertenece a este proceso.

Cree una variable entera con el nombre x y asígnele el tipo de datos int. Ahora, usamos el método read(), le proporcionamos el descriptor de archivo, &x, y el tamaño para leer el valor, y verificamos si su valor es menor que cero antes de mostrar un mensaje de error si es.

A continuación, verificamos si el valor fileDescriptor[1][1] es menor que cero y mostramos un error si lo es. Hacemos esto utilizando el método write(), pasándole sus parámetros y escribiendo usando el fileDescriptor[1][1].

Los descriptores de archivo para el primer proceso secundario deben cerrarse en el último paso.

if (processIdOne == 0) {
  // Child process 1
  close(fileDescriptor[0][1]);
  close(fileDescriptor[1][0]);
  close(fileDescriptor[2][0]);
  close(fileDescriptor[2][1]);
  int x;
  if (read(fileDescriptor[0][0], &x, sizeof(int)) < 0) {
    return 3;
  }
  x += 10;
  if (write(fileDescriptor[1][1], &x, sizeof(int)) < 0) {
    return 4;
  }
  close(fileDescriptor[0][0]);
  close(fileDescriptor[1][1]);
  return 0;
}

El siguiente paso es establecer una variable llamada processIdTwo y luego asignar la función fork(). Además, se debe realizar una verificación para asegurarse de que el valor de processIdTwo no sea igual o inferior a cero.

int processIdTwo = fork();
if (processIdTwo < 0) {
  return 5;
}

Ahora, verificamos si el processIdTwo tiene el valor cero; si lo hace, indica que estamos en el segundo proceso secundario. Dentro del segundo proceso secundario, debemos asegurarnos de que todos los descriptores de archivos se cierren utilizando el método close(), excepto este proceso en particular.

Declara una variable con el tipo de datos int llamada x.. Ahora, usamos el método read(), le damos el descriptor de archivo, &x, y el tamaño para leer el valor, y nos aseguramos de que se muestre un error si el valor es menor que cero.

A continuación, se aumenta el valor de x añadiéndole el valor 10, y usamos la función write() con sus parámetros para escribir usando el fileDescriptor[2][1]. Luego verificamos si su valor es menor que cero y, si lo es, mostramos un mensaje de error.

Por último, cierre los descriptores de archivo del segundo proceso secundario.

if (processIdTwo == 0) {
  close(fileDescriptor[0][0]);
  close(fileDescriptor[0][1]);
  close(fileDescriptor[1][1]);
  close(fileDescriptor[2][0]);
  int x;
  if (read(fileDescriptor[1][0], &x, sizeof(int)) < 0) {
    return 6;
  }
  x += 10;
  if (write(fileDescriptor[2][1], &x, sizeof(int)) < 0) {
    return 7;
  }
  close(fileDescriptor[1][0]);
  close(fileDescriptor[2][1]);
  return 0;
}

Como hemos hecho antes, ahora cerraremos todos los demás descriptores de archivo en el proceso principal excepto el correspondiente a este proceso.

Declare que la variable x sea del tipo entero. Ahora, usamos la función read(), le damos el descriptor de archivo, &x, y el tamaño para leer el valor, y verificamos si su valor es menor que cero para determinar si se debe mostrar un error.

A continuación, se incrementa el valor de x sumando el valor 10. Usamos la función write() con sus parámetros para escribir usando el fileDescriptor[2][0], y comprobamos si su valor es menor que cero para determinar si mostrar o no un mensaje de error.

Detenga los descriptores de archivos abiertos cerrándolos.

Tanto para processIdOne como para processIdTwo, se debe utilizar el método waitpid(). Esta función espera a que se completen todos los procesos antes de pasar a la siguiente operación.

close(fileDescriptor[0][0]);
close(fileDescriptor[1][0]);
close(fileDescriptor[1][1]);
close(fileDescriptor[2][1]);
int x = 0;
if (write(fileDescriptor[0][1], &x, sizeof(int)) < 0) {
  return 8;
}
if (read(fileDescriptor[2][0], &x, sizeof(int)) < 0) {
  return 9;
}
printf("The value received from the child processes is: %d\n", x);
close(fileDescriptor[0][1]);
close(fileDescriptor[2][0]);

waitpid(processIdOne, NULL, 0);
waitpid(processIdTwo, NULL, 0);

Código fuente completo para implementar múltiples tuberías en C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
  int fileDescriptor[3][2];
  int i;
  for (i = 0; i < 3; i++) {
    if (pipe(fileDescriptor[i]) < 0) {
      return 1;
    }
  }

  int processIdOne = fork();
  if (processIdOne < 0) {
    return 2;
  }

  if (processIdOne == 0) {
    // Child process 1
    close(fileDescriptor[0][1]);
    close(fileDescriptor[1][0]);
    close(fileDescriptor[2][0]);
    close(fileDescriptor[2][1]);
    int x;
    if (read(fileDescriptor[0][0], &x, sizeof(int)) < 0) {
      return 3;
    }
    x += 10;
    if (write(fileDescriptor[1][1], &x, sizeof(int)) < 0) {
      return 4;
    }
    close(fileDescriptor[0][0]);
    close(fileDescriptor[1][1]);
    return 0;
  }

  int processIdTwo = fork();
  if (processIdTwo < 0) {
    return 5;
  }

  if (processIdTwo == 0) {
    // Child process 2
    close(fileDescriptor[0][0]);
    close(fileDescriptor[0][1]);
    close(fileDescriptor[1][1]);
    close(fileDescriptor[2][0]);
    int x;
    if (read(fileDescriptor[1][0], &x, sizeof(int)) < 0) {
      return 6;
    }
    x += 10;
    if (write(fileDescriptor[2][1], &x, sizeof(int)) < 0) {
      return 7;
    }
    close(fileDescriptor[1][0]);
    close(fileDescriptor[2][1]);
    return 0;
  }

  // Parent process
  close(fileDescriptor[0][0]);
  close(fileDescriptor[1][0]);
  close(fileDescriptor[1][1]);
  close(fileDescriptor[2][1]);
  int x = 0;
  if (write(fileDescriptor[0][1], &x, sizeof(int)) < 0) {
    return 8;
  }
  if (read(fileDescriptor[2][0], &x, sizeof(int)) < 0) {
    return 9;
  }
  printf("The value received from the child processes is: %d\n", x);
  close(fileDescriptor[0][1]);
  close(fileDescriptor[2][0]);

  waitpid(processIdOne, NULL, 0);
  waitpid(processIdTwo, NULL, 0);

  return 0;
}

Producción :

The value received from the child processes is: 20
Waqar Aslam avatar Waqar Aslam avatar

I am Waqar having 5+ years of software engineering experience. I have been in the industry as a javascript web and mobile developer for 3 years working with multiple frameworks such as nodejs, react js, react native, Ionic, and angular js. After which I Switched to flutter mobile development. I have 2 years of experience building android and ios apps with flutter. For the backend, I have experience with rest APIs, Aws, and firebase. I have also written articles related to problem-solving and best practices in C, C++, Javascript, C#, and power shell.

LinkedIn

Artículo relacionado - C Pipe