📜  使用 fork() 设计一个并发服务器来处理多个客户端

📅  最后修改于: 2022-05-13 01:57:02.828000             🧑  作者: Mango

使用 fork() 设计一个并发服务器来处理多个客户端

先决条件: C/C++ 中的套接字编程,fork() 系统调用

问题陈述:在本文中,我们将编写一个程序来说明使用fork()系统调用的客户端-服务器模型,它可以同时处理多个客户端。

需要设计一个并发服务器来处理使用 fork() 调用的客户端:

通过 TCP 基本的服务器-客户端模型,一台服务器在特定时间只服务一个客户端

但是,我们现在正试图让我们的 TCP 服务器处理多个客户端。虽然,我们可以使用select() 系统调用来实现这一点,但我们可以简化整个过程。

fork() 系统调用将如何帮助解决这个问题?

Fork()创建一个与其父进程同步运行的新子进程,如果子进程创建成功则返回0

  • 每当一个新客户端尝试连接到 TCP 服务器时,我们将创建一个新的子进程,它将与其他客户端的执行并行运行。这样,我们将设计一个不使用Select() 系统调用的并发服务器。
  • pid_t(进程 ID)数据类型将用于保存子进程 ID。示例: pid_t = fork( )

与其他方法的区别:

这是创建并发服务器的最简单技术。每当一个新客户端连接到服务器时,都会执行一个 fork() 调用,为每个新客户端创建一个新的子进程。

  • 多线程使用单个处理程序实现并发服务器。与连接共享数据/文件通常使用fork()比使用线程慢。
  • Select() 系统调用不会创建多个进程。相反,它有助于在单个程序上多路复用所有客户端,并且不需要非阻塞 IO。

使用 fork() 设计并发服务器以处理多个客户端的程序

  • 接受客户端会创建一个新的子进程,该子进程与其他客户端和父进程同时运行
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);
}


C
// Server side program that sends
// a 'hi client' message
// to every client concurrently
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
// 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;
}


C
// Client Side program to test
// the TCP server that returns
// a 'hi client' message
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
// 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;
}


  • 服务器实现:

C

// Server side program that sends
// a 'hi client' message
// to every client concurrently
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
// 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;
}


  • 客户端实现:

C

// Client Side program to test
// the TCP server that returns
// a 'hi client' message
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
// 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;
}

编译脚本:

  • 执行服务器端代码
  • 执行客户端代码

输出:

优点:使用此工艺的优点是:

  • 易于在执行更复杂任务的程序中实现。
  • 每个子进程(客户端)独立运行,无法读取/写入其他客户端的数据。
  • 服务器的行为就像它只有一个客户端连接到它一样。子进程不需要关心其他传入连接或并行子进程的运行。因此,使用 fork() 系统调用进行编程是透明的并且花费更少的精力。

缺点:这里提到的缺点

  • Fork 的效率低于多线程,因为它通过创建新进程会产生很大的开销,但线程是一个轻量级进程,它共享来自父进程本身的资源。
  • 操作系统将需要内存共享或同步成本来实现并发。