Différences entre versions de « C pipe »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
 
(20 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
 
= Introduction =
 
= Introduction =
Un tube est un canal par lequel les informations circulent de manière uni-directionnel. Un processus écrit dans l'entrée du tube et un autre processus lit les informations en sortie.
+
Un tube est un canal par lequel les informations circulent de manière uni-directionnelle. Un processus écrit dans l'entrée du tube et un autre processus lit les informations en sortie.
  
 
= Manipulation des tubes =
 
= Manipulation des tubes =
Ligne 24 : Ligne 24 :
 
#include <unistd.h>
 
#include <unistd.h>
  
ssize_t write(int tube[0], const void *message, size_t longueur);
+
ssize_t write(int tube[1], const void *message, size_t longueur);
 
</source>
 
</source>
*tube[0] &rarr; le fichier descripteur de l'extrémité d'écriture
+
*tube[1] &rarr; le fichier descripteur de l'extrémité d'écriture
 
*message &rarr; le message à écrire
 
*message &rarr; le message à écrire
 
*longueur &rarr; la longueur du message
 
*longueur &rarr; la longueur du message
 
*ssize_t &rarr; le nombre d'octets écrits
 
*ssize_t &rarr; le nombre d'octets écrits
 +
 
== Lecture ==
 
== Lecture ==
 
Pour lire dans un tube :
 
Pour lire dans un tube :
Ligne 35 : Ligne 36 :
 
#include <unistd.h>
 
#include <unistd.h>
  
ssize_t read(int tube[1], void *message, size_t longueur);
+
ssize_t read(int tube[0], void *message, size_t longueur);
 
</source>
 
</source>
*tube[1] &rarr; le fichier descripteur de l'extrémité de lecture
+
*tube[0] &rarr; le fichier descripteur de l'extrémité de lecture
*message &rarr; un tableau de caractère qui contiendra le message à lire
+
*message &rarr; un tableau de caractères qui contiendra le message à lire
 
*longueur &rarr; la longueur du message à lire
 
*longueur &rarr; la longueur du message à lire
 
*ssize_t &rarr; le nombre d'octets lus
 
*ssize_t &rarr; le nombre d'octets lus
  
= Utilisation uni-directionnel =
+
==fermeture==
Ci-dessous un exemple qui permet au père de communiquer avec ces fils:
+
Un fois la lecture terminée on ferme le tube:
 +
<source lang="c">
 +
int close(int descripteur);
 +
</source>
 +
*descripteur : une extrémité du tube
 +
 
 +
= Utilisation dans un contexte d'exécution parallélisé =
 +
== Utilisation uni-directionnelle ==
 +
Ci-dessous un exemple qui permet au père de communiquer avec ses fils:
 
<source lang="c">
 
<source lang="c">
 
#include <stdio.h>
 
#include <stdio.h>
Ligne 72 : Ligne 81 :
 
sleep(1);
 
sleep(1);
 
}
 
}
exit(EXIT_SUCCESS);
 
 
}
 
}
 
// Fonction qui attend chacun des processus fils
 
// Fonction qui attend chacun des processus fils
Ligne 96 : Ligne 104 :
 
} else if (pid == 0) {
 
} else if (pid == 0) {
 
// On est dans le fils
 
// On est dans le fils
 +
close(tube[1]);
 
job(&tube[0]);
 
job(&tube[0]);
 +
close(tube[0]);
 +
exit(EXIT_SUCCESS);
 
} else {
 
} else {
 
// On est dans le père
 
// On est dans le père
Ligne 103 : Ligne 114 :
 
// Ecriture du message dans le tube
 
// Ecriture du message dans le tube
 
write(tube[1], message, LENGTH_MSG);
 
write(tube[1], message, LENGTH_MSG);
 +
close(tube[0]);
 +
close(tube[1]);
 +
}
 +
}
 +
waitForAll();
 +
return EXIT_SUCCESS;
 +
}
 +
</source>
 +
 +
== Utilisation bidirectionnelle ==
 +
Dans cet exemple, on crée une structure qui va contenir les deux tubes pour plus de facilité:
 +
<source lang="c">
 +
#include <stdio.h>
 +
#include <stdlib.h>
 +
#include <unistd.h>
 +
#include <sys/types.h>
 +
#include <sys/wait.h>
 +
#include <string.h>
 +
 +
// Structure avec des tubes full-duplex
 +
typedef struct fdpipe {
 +
int father[2];
 +
int son[2];
 +
} fdpipe;
 +
 +
// Nombre total de thread
 +
#define NB_FORK 10
 +
// Taille du message
 +
#define LENGTH_MSG 50
 +
// Tableau contenant le message
 +
char message[LENGTH_MSG] = "";
 +
 +
// Fonction exécutée dans le fork
 +
void job(fdpipe * pipes) {
 +
int tid = getpid();
 +
// timer pour attendre maximum 5 secondes
 +
int i = 5;
 +
while (i > 0) {
 +
// lecture dans le tube
 +
if (read((*pipes).father[0], message, LENGTH_MSG) > 0) {
 +
printf("Message du processus [%i] : %s\n", tid, message);
 +
// Ecriture du message dans le tableau
 +
sprintf(message, "je suis [%i] et j'ai bien reçu ton message !\n", tid);
 +
write((*pipes).son[1], message, LENGTH_MSG);
 +
break;
 +
}
 +
sleep(1);
 +
}
 +
}
 +
// Fonction qui attend chacun des processus fils
 +
void waitForAll() {
 +
int status;
 +
pid_t pid;
 +
int n = 0;
 +
while (n < NB_FORK) {
 +
pid = wait(&status);
 +
n++;
 +
}
 +
}
 +
 +
int main() {
 +
for (int i = 0; i < NB_FORK; i++) {
 +
fdpipe pipes;
 +
pipe(pipes.father);
 +
pipe(pipes.son);
 +
pid_t pid = fork();
 +
if (pid == -1) {
 +
// Il y a une erreur
 +
perror("fork");
 +
return EXIT_FAILURE;
 +
} else if (pid == 0) {
 +
// On est dans le fils
 +
close(pipes.father[1]);
 +
job(&pipes);
 +
close(pipes.father[0]);
 +
close(pipes.son[0]);
 +
close(pipes.son[1]);
 +
exit(EXIT_SUCCESS);
 +
} else {
 +
// On est dans le père
 +
// Ecriture du message dans le tableau
 +
sprintf(message, "Fork [%i], je suis ton père !\n", pid);
 +
// Ecriture du message dans le tube
 +
write(pipes.father[1], message, LENGTH_MSG);
 +
while (i > 0) {
 +
// lecture dans le tube
 +
if (read(pipes.son[0], message, LENGTH_MSG) > 0) {
 +
printf("Réponse du fils : %s\n", message);
 +
break;
 +
}
 +
sleep(1);
 +
}
 +
close(pipes.father[1]);
 +
close(pipes.father[0]);
 +
close(pipes.son[0]);
 +
close(pipes.son[1]);
 
}
 
}
 
}
 
}
Ligne 109 : Ligne 216 :
 
}
 
}
 
</source>
 
</source>
 +
Ce qui nous donne le résultat suivant :
 +
<pre>
 +
Message du processus [18523] : Fork [18523], je suis ton père !
 +
Réponse du fils : je suis [18523] et j'ai bien reçu ton message !
 +
Message du processus [18522] : Fork [18522], je suis ton père !
 +
Message du processus [18524] : Fork [18524], je suis ton père !
 +
Réponse du fils : je suis [18524] et j'ai bien reçu ton message !
 +
Message du processus [18525] : Fork [18525], je suis ton père !
 +
Réponse du fils : je suis [18525] et j'ai bien reçu ton message !
 +
</pre>
 +
 +
= Tube nommé =
 +
== Fonctions utilisées ==
 +
Il est possible de nommer un tube pour pouvoir y accéder depuis un autre processus. Pour ce faire nous allons utiliser les fonctions suivantes :
 +
<source lang="c">
 +
#include <sys/types.h>
 +
#include <sys/stat.h>
 +
 +
int mkfifo (const char* name, mode_t mode);
 +
</source>
 +
* name est le nom du tube ;
 +
* mode correspond aux permissions du tube (idem ''chmod'') ;
 +
* le code retour de la fonction varie entre :
 +
** EACCES : droits insuffisants pour créer le tube ;
 +
** EEXIST : un tube de même nom existe déjà ;
 +
** ENAMETOOLONG : nom trop long ;
 +
** ENOENT : le chemin n'existe pas ;
 +
** ENOSPC : espace insuffisant sur le système de fichiers.
 +
 +
<source lang="c">
 +
#include <fcntl.h>
 +
 +
int open (const char* name, int options);
 +
</source>
 +
* name est le nom du tube ;
 +
* options permet de désigner l'extrémité du tube:
 +
** O_WRONLY: pour l'entrée ;
 +
** O_RDONLY: pour la sortie.
 +
* le retour est le descripteur pour la lecture ou l'écriture ou -1 en cas d'échec.
 +
 +
== Exemple d'utilisation ==
 +
Ci-dessous un exemple d'utilisation avec un thread qui lit dans un tube nommé:
 +
<source lang="c">
 +
#include <stdio.h>
 +
#include <stdlib.h>
 +
#include <unistd.h>
 +
#include <sys/types.h>
 +
#include <sys/wait.h>
 +
#include <string.h>
 +
#include <pthread.h>
 +
#include <sys/types.h>
 +
#include <sys/stat.h>
 +
#include <fcntl.h>
 +
#include <errno.h>
 +
 +
// Nom du tube
 +
#define FIFO_NAME "/tmp/test.fifo"
 +
// Longeur du buffer de lecture
 +
#define BUFFER_LENGTH 30;
 +
// Mode de lecture du tube
 +
const mode_t FIFO_MODE = 0760;
 +
 +
// Fonction exécutée par le thread
 +
void * job(void * args) {
 +
// Récupération du descripteur en lecture
 +
int fdread;
 +
if ((fdread = open(FIFO_NAME, O_RDONLY)) == -1) {
 +
fprintf(stderr, "Impossible d'ouvrir le tube en lecture: %s\n",
 +
strerror(errno));
 +
exit(EXIT_FAILURE);
 +
}
 +
// Création d'un tampon pour stocker le contenu du tube
 +
char buffer[30];
 +
// Lecture du contenu du tube
 +
read(fdread, buffer, 30);
 +
// Affichage du contenu
 +
printf("Le tube contient : %s", buffer);
 +
// Fin de l'exécution du thread
 +
pthread_exit(EXIT_SUCCESS);
 +
}
 +
 +
int main() {
 +
// On supprime le tube s'il existe déjà
 +
if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
 +
printf("Suppression du tube existant: %s\n", FIFO_NAME);
 +
unlink(FIFO_NAME);
 +
}
 +
// Création de la variable qui va contenir le thread
 +
pthread_t thread;
 +
// Création du tube FIFO
 +
if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
 +
printf("Erreur de création du tube: %s\n", strerror(errno));
 +
exit(EXIT_FAILURE);
 +
}else{
 +
printf("Création du tube: %s\n", FIFO_NAME);
 +
}
 +
// Création du thread
 +
pthread_create(&thread, NULL, job, NULL);
 +
// Récupération du descripteur en écriture
 +
int fdwrite;
 +
if ((fdwrite = open(FIFO_NAME, O_WRONLY)) == -1) {
 +
printf("Impossible d'ouvrir le tube en écriture: %s\n",
 +
strerror(errno));
 +
exit(EXIT_FAILURE);
 +
}
 +
// Ecriture dans le tube
 +
char message[] = "Bonjour thread";
 +
write(fdwrite,  message, sizeof(message));
 +
// En attente de l'éxécution du thread
 +
pthread_join(thread, NULL);
 +
// Fin de l'exécution
 +
return EXIT_SUCCESS;
 +
}
 +
</source>
 +
{|style="width:800px" align="center"
 +
|
 +
[[Fichier:Warning manual.jpg|centré|300px]]
 +
|valign="top"|
 +
Dans l'exemple précédent, vous remarquerez que le thread est créé avant l'ouverture du tube. C'est absolument important de démarrer le thread avant, car la fonction ''open'' va bloquer le fil d'exécution principal tant que le thread n'ouvre pas le tube en lecture ! Pour eviter cela il faut utiliser l'option '''O_NONBLOCK'''
 +
|}

Version actuelle datée du 14 novembre 2020 à 18:16

Introduction

Un tube est un canal par lequel les informations circulent de manière uni-directionnelle. Un processus écrit dans l'entrée du tube et un autre processus lit les informations en sortie.

Manipulation des tubes

Création

La première étape est la création d'un tube:

#include <unistd.h>

int tube[2];

int pipe(int tube[2]);
  • tube[0] → contiendra le fichier descripteur de l'extrémité de lecture
  • tube[1] → contiendra le fichier descripteur de l'extrémité d'écriture
  • Le retour sera :
    • '0' si tout s'est bien passé
    • '1' si une erreur survient et errno est positionné

Écriture

Pour écrire dans un tube :

#include <unistd.h>

ssize_t write(int tube[1], const void *message, size_t longueur);
  • tube[1] → le fichier descripteur de l'extrémité d'écriture
  • message → le message à écrire
  • longueur → la longueur du message
  • ssize_t → le nombre d'octets écrits

Lecture

Pour lire dans un tube :

#include <unistd.h>

ssize_t read(int tube[0], void *message, size_t longueur);
  • tube[0] → le fichier descripteur de l'extrémité de lecture
  • message → un tableau de caractères qui contiendra le message à lire
  • longueur → la longueur du message à lire
  • ssize_t → le nombre d'octets lus

fermeture

Un fois la lecture terminée on ferme le tube:

int close(int descripteur);
  • descripteur : une extrémité du tube

Utilisation dans un contexte d'exécution parallélisé

Utilisation uni-directionnelle

Ci-dessous un exemple qui permet au père de communiquer avec ses fils:

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

// Nombre total de thread
#define NB_FORK 2
// Taille du message
#define LENGTH_MSG 30
// Tableau contenant le message
char message[LENGTH_MSG] = "";

// Fonction exécutée dans le fork
void job(int * tube) {
	int tid = getpid();
	// timer pour attendre maximum 5 secondes
	int i = 5;
	while (i > 0) {
		// lecture dans le tube
		if (read(*tube, message, LENGTH_MSG) > 0) {
			printf("Message du processus [%i] : %s", tid, message);
			break;
		}
		sleep(1);
	}
}
// Fonction qui attend chacun des processus fils
void waitForAll() {
	int status;
	pid_t pid;
	int n = 0;
	while (n < NB_FORK) {
		pid = wait(&status);
		n++;
	}
}

int main() {
	for (int i = 0; i < NB_FORK; i++) {
		int tube[2];
		pipe(tube);
		pid_t pid = fork();
		if (pid == -1) {
			// Il y a une erreur
			perror("fork");
			return EXIT_FAILURE;
		} else if (pid == 0) {
			// On est dans le fils
			close(tube[1]);
			job(&tube[0]);
			close(tube[0]);
			exit(EXIT_SUCCESS);
		} else {
			// On est dans le père
			// Ecriture du message dans le tableau
			sprintf(message, "Fork [%i], je suis ton père !\n", pid);
			// Ecriture du message dans le tube
			write(tube[1], message, LENGTH_MSG);
			close(tube[0]);
			close(tube[1]);
		}
	}
	waitForAll();
	return EXIT_SUCCESS;
}

Utilisation bidirectionnelle

Dans cet exemple, on crée une structure qui va contenir les deux tubes pour plus de facilité:

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

// Structure avec des tubes full-duplex
typedef struct fdpipe {
	int father[2];
	int son[2];
} fdpipe;

// Nombre total de thread
#define NB_FORK 10
// Taille du message
#define LENGTH_MSG 50
// Tableau contenant le message
char message[LENGTH_MSG] = "";

// Fonction exécutée dans le fork
void job(fdpipe * pipes) {
	int tid = getpid();
	// timer pour attendre maximum 5 secondes
	int i = 5;
	while (i > 0) {
		// lecture dans le tube
		if (read((*pipes).father[0], message, LENGTH_MSG) > 0) {
			printf("Message du processus [%i] : %s\n", tid, message);
			// Ecriture du message dans le tableau
			sprintf(message, "je suis [%i] et j'ai bien reçu ton message !\n", tid);
			write((*pipes).son[1], message, LENGTH_MSG);
			break;
		}
		sleep(1);
	}
}
// Fonction qui attend chacun des processus fils
void waitForAll() {
	int status;
	pid_t pid;
	int n = 0;
	while (n < NB_FORK) {
		pid = wait(&status);
		n++;
	}
}

int main() {
	for (int i = 0; i < NB_FORK; i++) {
		fdpipe pipes;
		pipe(pipes.father);
		pipe(pipes.son);
		pid_t pid = fork();
		if (pid == -1) {
			// Il y a une erreur
			perror("fork");
			return EXIT_FAILURE;
		} else if (pid == 0) {
			// On est dans le fils
			close(pipes.father[1]);
			job(&pipes);
			close(pipes.father[0]);
			close(pipes.son[0]);
			close(pipes.son[1]);
			exit(EXIT_SUCCESS);
		} else {
			// On est dans le père
			// Ecriture du message dans le tableau
			sprintf(message, "Fork [%i], je suis ton père !\n", pid);
			// Ecriture du message dans le tube
			write(pipes.father[1], message, LENGTH_MSG);
			while (i > 0) {
				// lecture dans le tube
				if (read(pipes.son[0], message, LENGTH_MSG) > 0) {
					printf("Réponse du fils : %s\n", message);
					break;
				}
				sleep(1);
			}
			close(pipes.father[1]);
			close(pipes.father[0]);
			close(pipes.son[0]);
			close(pipes.son[1]);
		}
	}
	waitForAll();
	return EXIT_SUCCESS;
}

Ce qui nous donne le résultat suivant :

Message du processus [18523] : Fork [18523], je suis ton père !
Réponse du fils : je suis [18523] et j'ai bien reçu ton message !
Message du processus [18522] : Fork [18522], je suis ton père !
Message du processus [18524] : Fork [18524], je suis ton père !
Réponse du fils : je suis [18524] et j'ai bien reçu ton message !
Message du processus [18525] : Fork [18525], je suis ton père !
Réponse du fils : je suis [18525] et j'ai bien reçu ton message !

Tube nommé

Fonctions utilisées

Il est possible de nommer un tube pour pouvoir y accéder depuis un autre processus. Pour ce faire nous allons utiliser les fonctions suivantes :

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo (const char* name, mode_t mode);
  • name est le nom du tube ;
  • mode correspond aux permissions du tube (idem chmod) ;
  • le code retour de la fonction varie entre :
    • EACCES : droits insuffisants pour créer le tube ;
    • EEXIST : un tube de même nom existe déjà ;
    • ENAMETOOLONG : nom trop long ;
    • ENOENT : le chemin n'existe pas ;
    • ENOSPC : espace insuffisant sur le système de fichiers.
#include <fcntl.h>

int open (const char* name, int options);
  • name est le nom du tube ;
  • options permet de désigner l'extrémité du tube:
    • O_WRONLY: pour l'entrée ;
    • O_RDONLY: pour la sortie.
  • le retour est le descripteur pour la lecture ou l'écriture ou -1 en cas d'échec.

Exemple d'utilisation

Ci-dessous un exemple d'utilisation avec un thread qui lit dans un tube nommé:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

// Nom du tube
#define FIFO_NAME "/tmp/test.fifo"
// Longeur du buffer de lecture
#define BUFFER_LENGTH 30;
// Mode de lecture du tube
const mode_t FIFO_MODE = 0760;

// Fonction exécutée par le thread
void * job(void * args) {
	// Récupération du descripteur en lecture
	int fdread;
	if ((fdread = open(FIFO_NAME, O_RDONLY)) == -1) {
		fprintf(stderr, "Impossible d'ouvrir le tube en lecture: %s\n",
				strerror(errno));
		exit(EXIT_FAILURE);
	}
	// Création d'un tampon pour stocker le contenu du tube
	char buffer[30];
	// Lecture du contenu du tube
	read(fdread, buffer, 30);
	// Affichage du contenu
	printf("Le tube contient : %s", buffer);
	// Fin de l'exécution du thread
	pthread_exit(EXIT_SUCCESS);
}

int main() {
	// On supprime le tube s'il existe déjà
	if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
		printf("Suppression du tube existant: %s\n", FIFO_NAME);
		unlink(FIFO_NAME);
	}
	// Création de la variable qui va contenir le thread
	pthread_t thread;
	// Création du tube FIFO
	if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
		printf("Erreur de création du tube: %s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}else{
		printf("Création du tube: %s\n", FIFO_NAME);
	}
	// Création du thread
	pthread_create(&thread, NULL, job, NULL);
	// Récupération du descripteur en écriture
	int fdwrite;
	if ((fdwrite = open(FIFO_NAME, O_WRONLY)) == -1) {
		printf("Impossible d'ouvrir le tube en écriture: %s\n",
				strerror(errno));
		exit(EXIT_FAILURE);
	}
	// Ecriture dans le tube
	char message[] = "Bonjour thread";
	write(fdwrite,  message, sizeof(message));
	// En attente de l'éxécution du thread
	pthread_join(thread, NULL);
	// Fin de l'exécution
	return EXIT_SUCCESS;
}
Warning manual.jpg

Dans l'exemple précédent, vous remarquerez que le thread est créé avant l'ouverture du tube. C'est absolument important de démarrer le thread avant, car la fonction open va bloquer le fil d'exécution principal tant que le thread n'ouvre pas le tube en lecture ! Pour eviter cela il faut utiliser l'option O_NONBLOCK