Le multi processus

La fonction fork()

Sous Unix, on peut créer une nouvelle tâche en dupliquant un processus via l'appel de la fonction fork(). Cette fonction ne prend aucun paramètre, duplique le processus et retourne une valeur différente au processus père et à sa copie le processus fils.

Le nouveau processus dispose du même code à exécuter et d'une copie de l'état des données réalisée au momment de la création.

Exemple

#include <stdio.h>
#include <unistd.h>		// pour la fonction fork

int main()
{
	pid_t pid;

	int a;

	a=10;
	pid=fork();
	switch(pid)
	{
		case -1: // en cas d'erreur
			printf("Erreur de création de processus fils");
			return(1);
		case 0: // le fils
			printf("Le processus fils : a vaut au départ %d\n",a);
			a=20;
			sleep(1);
			printf("Le processus fils : a vaut ensuite %d\n",a);
			sleep(1);
			return(0);
		default : // le père
			printf("Le processus père : a vaut au départ %d\n",a);
			a=30;
			sleep(1);
			printf("Le processus père : a vaut ensuite %d\n",a);
			sleep(1);
			return(0);			
	}
}
	

Le partage des sockets

Quand on crée un processus fils, il récupère une copie de l'espace système du processus père et donc, outre la copie des variables d'environnement, la copie de tous les descripteurs de fichiers ouverts par le père avant l'appel à la fonction fork(). Par conséquence, tous les sockets du père sont également accessibles par le fils.

Après l'appel de la fonction fork(), le père doit fermer via la fonction close() les descripteurs dont il n'a plus besoin tandis que le fils doit fermer ceux qui sont gérés par le père et dont le fils n'a plus besoin.

Remarque : Dès lors qu'un socket reste ouvert sur l'un des processus de la machine faisant tourner l'application serveur, le client y reste connecté.

Exemple

Voici l'exemple d'un serveur qui renvoie, en echo, ce qu'il reçoit du client.

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// pour la fonction bzero
#include <arpa/inet.h>	// pour la fonction inet_addr

#include <unistd.h>		// pour la fonction fork

void communication_serveur(int fd) {
	int len;
	char buf[100];

	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Erreur de creation de la socket !\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Adresse IPv4 invalide !\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Erreur d'accès au port serveur !\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Erreur de création de la file d'attente !\n");
		exit(1);
	}
	printf("Serveur lancé !\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connexion acceptée !\n");
			if (fork()==0) {
				close(fd_srv);
				communication_serveur(fd_con);
				shutdown(fd_con, SHUT_RDWR);
				close(fd_con);
				printf("Client déconnecté par le serveur !\n");
				exit(0);
			}
			close(fd_con);
		}
	}
	return 0;
}
	

Destruction automatique des zombies

Quand un processus se termine, il passe dans l'état zombie jusqu'à ce que son processus père récupère le code de retour via la fonction bloquante waitpid(). Le père qui reçoit les demandes de connexion ne peut se permettre de rester bloqué dans l'exécution d'un waitpid(). Mais un programme serveur ne peut pas non plus laisser le système se remplir avec des processus dans l'état zombie. Heureusement, lorsqu'un processus se termine il envoie un signal SIGCHLD à son père. Dans une fonction conçue pour traiter ce signal, on pourra exécuter la fonction waitpid(). On utilisera la fonction signal() pour fixer la fonction qui sera appelée lors de la réception du signal SIGCHLD.

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// pour la fonction bzero
#include <arpa/inet.h>	// pour la fonction inet_addr

#include <unistd.h>		// pour la fonction fork
#include <signal.h>		// pour la fonction signal
#include <sys/wait.h>		// pour la fonction waitpid

void sigchld_handler(int signo) {
	signal(SIGCHLD, sigchld_handler);
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

void communication_serveur(int fd) {
	int len;
	char buf[100];

	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Erreur de creation de la socket !\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Adresse IPv4 invalide !\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Erreur d'accès au port serveur !\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Erreur de création de la file d'attente !\n");
		exit(1);
	}
	signal(SIGCHLD, sigchld_handler);
	printf("Serveur lancé !\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connexion acceptée !\n");
			if (fork()==0) {
				close(fd_srv);
				communication_serveur(fd_con);
				shutdown(fd_con, SHUT_RDWR);
				close(fd_con);
				printf("Client déconnecté par le serveur !\n");
				exit(0);
			}
			close(fd_con);
		}
	}
	return 0;
}