Diseñe un servidor concurrente para manejar múltiples clientes usando fork()

Prerrequisito: Programación de sockets en C/C++ , fork() Llamada al sistema 

Declaración del problema: en este artículo, vamos a escribir un programa que ilustra el modelo cliente-servidor usando una llamada al sistema fork() que puede manejar varios clientes al mismo tiempo. 

La llamada Fork() crea múltiples procesos secundarios para clientes concurrentes y ejecuta cada bloque de llamada en su propio bloque de control de procesos (PCB).

Necesidad de diseñar un servidor concurrente para manejar clientes usando la llamada fork():

A través del modelo TCP básico servidor-cliente, un servidor atiende a un solo cliente en un momento determinado .

Pero ahora estamos tratando de hacer que nuestro servidor TCP maneje más de un cliente. Aunque podemos lograr esto usando la llamada al sistema select() pero podemos facilitar todo el proceso.

¿Cómo va a ayudar la llamada al sistema fork() en esto?

Fork() crea un nuevo proceso secundario que se ejecuta en sincronización con su proceso principal y devuelve 0 si el proceso secundario se crea correctamente.

  • Siempre que un nuevo cliente intente conectarse al servidor TCP, crearemos un nuevo proceso secundario que se ejecutará en paralelo con la ejecución de otros clientes. De esta forma, vamos a diseñar un servidor concurrente sin utilizar la llamada al sistema Select().
  • Se utilizará un tipo de datos pid_t (Id. de proceso) para contener la Id. de proceso del niño. Ejemplo: pid_t = tenedor( ) .

Diferencia de los otros enfoques:

Esta es la técnica más simple para crear un servidor concurrente. Cada vez que un nuevo cliente se conecta al servidor, se ejecuta una llamada fork() creando un nuevo proceso secundario para cada nuevo cliente.

  • Multi-Threading logra un servidor concurrente usando un solo programa procesado. Compartir datos/archivos con conexiones suele ser más lento con una bifurcación() que con subprocesos.
  • La llamada al sistema Select() no crea múltiples procesos. En cambio, ayuda a multiplexar todos los clientes en un solo programa y no necesita E/S sin bloqueo.

Programa para diseñar un servidor concurrente para manejar múltiples clientes usando fork()

  • Aceptar un cliente crea un nuevo proceso secundario que se ejecuta simultáneamente con otros clientes y el proceso principal

C

// Accept connection request from client in cliAddr
// socket structure
clientSocket = accept(
    sockfd, (struct sockaddr*)&cliAddr, &addr_size);
 
// Make a child process by fork() and check if child
// process is created successfully
if ((childpid = fork()) == 0) {
    // Send a confirmation message to the client for
    // successful connection
    send(clientSocket, "hi client", strlen("hi client"),
         0);
}
  • Implementación del servidor:

C

// Server side program that sends
// a 'hi client' message
// to every client concurrently
 
#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>
 
// PORT number
#define PORT 4444
 
int main()
{
    // Server socket id
    int sockfd, ret;
 
    // Server socket address structures
    struct sockaddr_in serverAddr;
 
    // Client socket id
    int clientSocket;
 
    // Client socket address structures
    struct sockaddr_in cliAddr;
 
    // Stores byte size of server socket address
    socklen_t addr_size;
 
    // Child process id
    pid_t childpid;
 
    // Creates a TCP socket id from IPV4 family
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
    // Error handling if socket id is not valid
    if (sockfd < 0) {
        printf("Error in connection.\n");
        exit(1);
    }
 
    printf("Server Socket is created.\n");
 
    // Initializing address structure with NULL
    memset(&serverAddr, '\0',
           sizeof(serverAddr));
 
    // Assign port number and IP address
    // to the socket created
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
 
    // 127.0.0.1 is a loopback address
    serverAddr.sin_addr.s_addr
        = inet_addr("127.0.0.1");
 
    // Binding the socket id with
    // the socket structure
    ret = bind(sockfd,
               (struct sockaddr*)&serverAddr,
               sizeof(serverAddr));
 
    // Error handling
    if (ret < 0) {
        printf("Error in binding.\n");
        exit(1);
    }
 
    // Listening for connections (upto 10)
    if (listen(sockfd, 10) == 0) {
        printf("Listening...\n\n");
    }
 
    int cnt = 0;
    while (1) {
 
        // Accept clients and
        // store their information in cliAddr
        clientSocket = accept(
            sockfd, (struct sockaddr*)&cliAddr,
            &addr_size);
 
        // Error handling
        if (clientSocket < 0) {
            exit(1);
        }
 
        // Displaying information of
        // connected client
        printf("Connection accepted from %s:%d\n",
               inet_ntoa(cliAddr.sin_addr),
               ntohs(cliAddr.sin_port));
 
        // Print number of clients
        // connected till now
        printf("Clients connected: %d\n\n",
               ++cnt);
 
        // Creates a child process
        if ((childpid = fork()) == 0) {
 
            // Closing the server socket id
            close(sockfd);
 
            // Send a confirmation message
            // to the client
            send(clientSocket, "hi client",
                 strlen("hi client"), 0);
        }
    }
 
    // Close the client socket id
    close(clientSocket);
    return 0;
}
  • Implementación del cliente:

C

// Client Side program to test
// the TCP server that returns
// a 'hi client' message
 
#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>
 
// PORT number
#define PORT 4444
 
int main()
{
    // Socket id
    int clientSocket, ret;
 
    // Client socket structure
    struct sockaddr_in cliAddr;
 
    // char array to store incoming message
    char buffer[1024];
 
    // Creating socket id
    clientSocket = socket(AF_INET,
                          SOCK_STREAM, 0);
 
    if (clientSocket < 0) {
        printf("Error in connection.\n");
        exit(1);
    }
    printf("Client Socket is created.\n");
 
    // Initializing socket structure with NULL
    memset(&cliAddr, '\0', sizeof(cliAddr));
 
    // Initializing buffer array with NULL
    memset(buffer, '\0', sizeof(buffer));
 
    // Assigning port number and IP address
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
 
    // 127.0.0.1 is Loopback IP
    serverAddr.sin_addr.s_addr
        = inet_addr("127.0.0.1");
 
    // connect() to connect to the server
    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) {
 
        // recv() receives the message
        // from server and stores in buffer
        if (recv(clientSocket, buffer, 1024, 0)
            < 0) {
            printf("Error in receiving data.\n");
        }
 
        // Printing the message on screen
        else {
            printf("Server: %s\n", buffer);
            bzero(buffer, sizeof(buffer));
        }
    }
 
    return 0;
}

Compilar secuencia de comandos:

  • Ejecutando código del lado del servidor

gcc servidor.c -o ser
   ./ser

  • Ejecutando código del lado del cliente

gcc cliente.c -o cli
  ./cli

Producción:

Ventajas: Las ventajas de utilizar este proceso son:

  • Fácil de implementar en un programa que realiza una tarea mucho más compleja.
  • Cada proceso secundario (cliente) se ejecuta de forma independiente y no puede leer/escribir datos de otros clientes.
  • El servidor se comporta como si solo tuviera un cliente conectado. Los procesos secundarios no necesitan preocuparse por otras conexiones entrantes o la ejecución de procesos secundarios paralelos. Por lo tanto, la programación con la llamada al sistema fork() es transparente y requiere menos esfuerzo.

Desventajas: Las desventajas son las mencionadas aquí .

  • Fork es menos eficiente que los subprocesos múltiples porque crea una gran sobrecarga al crear un nuevo proceso, pero un subproceso es un proceso liviano que comparte los recursos del proceso principal.
  • El sistema operativo necesitará un costo de sincronización o uso compartido de memoria para lograr la concurrencia.

Publicación traducida automáticamente

Artículo escrito por sranabe19 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *