Multiprocessing

The fork() function

In Unix, a new task can be created by duplicating a process via a call to the fork() function. This function takes no parameters, duplicates the process, and returns a different value to the parent process and to its copy, the child process.

The new process has the same code to execute and a copy of the data state at the time of creation.

Example

#include <stdio.h>
#include <unistd.h>		// for the function fork

int main()
{
	pid_t pid;

	int a;

	a=10;
	pid=fork();
	switch(pid)
	{
		case -1: // In case of error
			printf("child process creation error");
			return(1);
		case 0: // The child process
			printf("The child process: a is at the start %d\n",a);
			a=20;
			sleep(1);
			printf("The child process: a is then %d\n",a);
			sleep(1);
			return(0);
		default : // The parent process
			printf("The parent process: a is initially %d\n",a);
			a=30;
			sleep(1);
			printf("The parent process: a is then %d\n",a);
			sleep(1);
			return(0);			
	}
}
	

Sharing sockets

When creating a child process, it inherits a copy of the parent process's system space, and thus, in addition to copying the environment variables, it also copies all the file descriptors opened by the parent before the call to the fork() function. As a result, all the sockets of the parent are also accessible by the child.

After the call to the fork() function, the parent must close the descriptors it no longer needs via the close() function, while the child must close those that are managed by the parent and that the child no longer needs.

Note: As long as a socket remains open on one of the processes running the server application, the client remains connected.

Example

Here is an example of a server that echoes back to the client what it receives.

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

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

#include <string.h>		// for the function bzero
#include <arpa/inet.h>	// for the function inet_addr

#include <unistd.h>		// for the function fork

void server_dialog(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("Socket creation error!\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("Invalid IPv4 address!\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Server port access error!\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Queue creation error!\n");
		exit(1);
	}
	printf("Server launched!\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("Connection accepted!\n");
			if (fork()==0) {
				close(fd_srv);
				server_dialog(fd_con);
				shutdown(fd_con, SHUT_RDWR);
				close(fd_con);
				printf("Client disconnected by server!\n");
				exit(0);
			}
			close(fd_con);
		}
	}
	return 0;
}
	

Automatic Zombie Destruction

When a process terminates, it goes into the zombie state until its parent process retrieves the return code via the blocking function waitpid(). The parent that receives connection requests cannot afford to stay blocked in the execution of a waitpid(). But a server program cannot also let the system fill up with processes in the zombie state. Fortunately, when a process terminates it sends a SIGCHLD signal to its parent. In a function designed to handle this signal, it will be possible to execute the waitpid() function. The function signal() will be used to set the function that will be called upon receipt of the SIGCHLD signal.

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

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

#include <string.h>		// for the function bzero
#include <arpa/inet.h>	// for the function inet_addr

#include <unistd.h>		// for the function fork
#include <signal.h>		// for the function signal
#include <sys/wait.h>		// for the function waitpid

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

void server_dialog(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("Socket creation error!\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("Invalid IPv4 address!\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Server port access error!\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Queue creation error!\n");
		exit(1);
	}
	signal(SIGCHLD, sigchld_handler);
	printf("Server launched!\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("Connection accepted!\n");
			if (fork()==0) {
				close(fd_srv);
				server_dialog(fd_con);
				shutdown(fd_con, SHUT_RDWR);
				close(fd_con);
				printf("Client disconnected by server!\n");
				exit(0);
			}
			close(fd_con);
		}
	}
	return 0;
}