Prerequisite: Socket Programming in C/C++, fork() System call
Problem Statement: In this article, we are going to write a program that illustrates the Client-Server Model using fork() system call which can handle multiple clients concurrently.
Fork() call creates multiple child processes for concurrent clients and runs each call block in its own process control block (PCB).
Need for designing a concurrent server for handling clients using fork() call:
Through TCP basic server-client model, one server attends only one client at a particular time.
But, we are now trying to make our TCP server handle more than one client. Although, we can achieve this using select() system call but we can ease the whole process.
How is the fork() system call going to help in this?
Fork() creates a new child process that runs in sync with its Parent process and returns 0 if child process is created successfully.
- Whenever a new client will attempt to connect to the TCP server, we will create a new Child Process that is going to run in parallel with other clients’ execution. In this way, we are going to design a concurrent server without using the Select() system call.
- A pid_t (Process id) data type will be used to hold the Child’s process id. Example: pid_t = fork( ).
Difference from the other approaches:
This is the simplest technique for creating a concurrent server. Whenever a new client connects to the server, a fork() call is executed making a new child process for each new client.
- Multi-Threading achieves a concurrent server using a single processed program. Sharing of data/files with connections is usually slower with a fork() than with threads.
- Select() system call doesn’t create multiple processes. Instead, it helps in multiplexing all the clients on a single program and doesn’t need non-blocking IO.
Program to design a concurrent server for handling multiple clients using fork()
- Accepting a client makes a new child process that runs concurrently with other clients and the parent process
C
clientSocket = accept(
sockfd, ( struct sockaddr*)&cliAddr, &addr_size);
if ((childpid = fork()) == 0) {
send(clientSocket, "hi client" , strlen ( "hi client" ),
0);
}
|
C
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 4444
int main()
{
int sockfd, ret;
struct sockaddr_in serverAddr;
int clientSocket;
struct sockaddr_in cliAddr;
socklen_t addr_size;
pid_t childpid;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
printf ( "Error in connection.\n" );
exit (1);
}
printf ( "Server Socket is created.\n" );
memset (&serverAddr, '\0' ,
sizeof (serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr
= inet_addr( "127.0.0.1" );
ret = bind(sockfd,
( struct sockaddr*)&serverAddr,
sizeof (serverAddr));
if (ret < 0) {
printf ( "Error in binding.\n" );
exit (1);
}
if (listen(sockfd, 10) == 0) {
printf ( "Listening...\n\n" );
}
int cnt = 0;
while (1) {
clientSocket = accept(
sockfd, ( struct sockaddr*)&cliAddr,
&addr_size);
if (clientSocket < 0) {
exit (1);
}
printf ( "Connection accepted from %s:%d\n" ,
inet_ntoa(cliAddr.sin_addr),
ntohs(cliAddr.sin_port));
printf ( "Clients connected: %d\n\n" ,
++cnt);
if ((childpid = fork()) == 0) {
close(sockfd);
send(clientSocket, "hi client" ,
strlen ( "hi client" ), 0);
}
}
close(clientSocket);
return 0;
}
|
C
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 4444
int main()
{
int clientSocket, ret;
struct sockaddr_in cliAddr;
char buffer[1024];
clientSocket = socket(AF_INET,
SOCK_STREAM, 0);
if (clientSocket < 0) {
printf ( "Error in connection.\n" );
exit (1);
}
printf ( "Client Socket is created.\n" );
memset (&cliAddr, '\0' , sizeof (cliAddr));
memset (buffer, '\0' , sizeof (buffer));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr
= inet_addr( "127.0.0.1" );
ret = connect(clientSocket,
( struct sockaddr*)&serverAddr,
sizeof (serverAddr));
if (ret < 0) {
printf ( "Error in connection.\n" );
exit (1);
}
printf ( "Connected to Server.\n" );
while (1) {
if (recv(clientSocket, buffer, 1024, 0)
< 0) {
printf ( "Error in receiving data.\n" );
}
else {
printf ( "Server: %s\n" , buffer);
bzero(buffer, sizeof (buffer));
}
}
return 0;
}
|
Compile script:
- Executing server side code
⇒ gcc server.c -o ser
./ser
- Executing client side code
⇒ gcc client.c -o cli
./cli
Output:

Advantages: The advantages of using this process are:
- Easy to implement in a program doing a far more complex task.
- Each child process (client) runs independently and is unable to read/write other clients’ data.
- The server behaves as it has only one client connected to it. The child processes need not care about other incoming connections or the running of parallel child processes. Therefore, programming with fork() system call is transparent and takes less effort.
Disadvantages: The disadvantages are as mentioned here
- Fork is less efficient than multi-threading because it creates a large overhead by creating a new process but a thread is a lightweight process that shares the resources from the parent process itself.
- Operating system will need memory sharing or synchronization cost for achieving concurrency.