高并发服务器-使用多进程(Multi-Process)实现【C语言】

在上期的socket套接字的使用详解中(socket套接字的使用详解)最后实现的TCP服务器只能处理一个客户端的请求发送,当有其他客户端请求连接时会被阻塞。为了能同时处理多个客户端的连接请求,本期使用多进程的方式来解决。

解决方案步骤总结

  1. 初始化服务器

    • 创建监听套接字(socket)。
    • 绑定套接字到指定地址和端口(bind)。
    • 开始监听连接请求(listen)。
  2. 等待连接

    • 进入一个无限循环,等待并接受客户端连接(accept)。
  3. 创建子进程

    • 每当接受到一个新的客户端连接,创建一个子进程(fork)。
    • 子进程负责与客户端通信,处理请求并发送响应。
  4. 父进程继续监听

    • 父进程关闭与客户端通信的套接字,继续监听新的连接请求。
  5. 处理子进程结束信号(可选)

    • 注册信号处理函数,处理子进程结束信号,避免僵尸进程。

示例代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>

// 处理SIGCHLD信号,避免僵尸进程
void sigchld_handler(int signo) {
    while (waitpid(-1, NULL, WNOHANG) > 0); //表示非阻塞地等待任意子进程终止。-1 表示等待任何子进程,NULL 表示不需要子进程的退出状态,WNOHANG 表示非阻塞。
}

// 处理客户端通信
void handle_client(int cfd) {
    char buf[1024];
    int n;
    while ((n = read(cfd, buf, sizeof(buf))) > 0) {
        for (int i = 0; i < n; i++) {
            buf[i] = toupper(buf[i]);
        }
        write(cfd, buf, n);
    }
    close(cfd);
}

int main() {
    // 创建监听套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd < 0) {
        perror("socket error");
        return -1;
    }

    // 绑定套接字
    struct sockaddr_in serv;
    bzero(&serv, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8888);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(lfd, (struct sockaddr *)&serv, sizeof(serv)) < 0) {
        perror("bind error");
        return -1;
    }

    // 监听连接请求
    listen(lfd, 3);

    // 设置SIGCHLD信号处理
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);           // 初始化信号屏蔽字为空。
    sa.sa_flags = SA_RESTART;           //设置信号处理之后自动重新启动被信号打断的系统调用。
    if (sigaction(SIGCHLD, &sa, NULL) < 0) {
        perror("sigaction error");
        return -1;
    }

    while (1) {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int cfd = accept(lfd, (struct sockaddr *)&client, &len);
        if (cfd < 0) {
            perror("accept error");
            continue;
        }

        // 打印客户端连接信息
        char sIP[16];
        memset(sIP, 0x00, sizeof(sIP));
        printf("Client connected: IP [%s], PORT [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));

        pid_t pid = fork();
        if (pid == 0) { // 子进程
            close(lfd); // 子进程关闭监听套接字
            handle_client(cfd); // 处理客户端通信
            printf("Client disconnected: IP [%s], PORT [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
            exit(0); // 子进程处理完成后退出
        } else if (pid > 0) { // 父进程
            close(cfd); // 父进程关闭与客户端通信的套接字
        } else {
            perror("fork error");
            close(cfd);
        }
    }

    close(lfd);
    return 0;
}

客户端:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8888
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"

int main() {
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    char input_buffer[BUFFER_SIZE] = {0};
    char *hello = "Hello from client";
    int opt = 1;

    // 创建 TCP 套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation failed");
        return -1;
    }

    // 设置服务器地址结构
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将 IPv4 地址从文本转换为二进制形式
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        return -1;
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        return -1;
    }

    printf("Connected to server\n");

    // 循环发送消息并接收响应
    while (1) {
        printf("Enter message to send (or 'exit' to quit): ");
        fgets(input_buffer, BUFFER_SIZE, stdin);

        // 去掉输入的换行符
        input_buffer[strcspn(input_buffer, "\n")] = 0;

        // 如果输入是 'exit',则退出循环
        if (strcmp(input_buffer, "exit") == 0) {
            break;
        }

        // 发送消息给服务器
        send(sock, input_buffer, strlen(input_buffer), 0);
        printf("Message sent to server: %s\n", input_buffer);

        // 接收服务器的响应
        valread = read(sock, buffer, BUFFER_SIZE);
        printf("Server response: %s\n", buffer);
        memset(buffer, 0, sizeof(buffer));
    }

    close(sock);
    return 0;
}
相关推荐
phltxy19 分钟前
从零入门JavaScript:基础语法全解析
开发语言·javascript
带土124 分钟前
5. enum(枚举)关键字在C/C++中的作用
c语言·c++
BD_Marathon32 分钟前
SpringBoot——辅助功能之切换web服务器
服务器·前端·spring boot
天“码”行空1 小时前
java面向对象的三大特性之一多态
java·开发语言·jvm
晚风吹人醒.1 小时前
SSH远程管理及访问控制
linux·运维·ssh·scp·xshell·访问控制·远程管理
AI大模型应用之禅2 小时前
全球股市估值与可持续农业垂直种植技术的关系
网络·ai
掘根2 小时前
【仿Muduo库项目】HTTP模块2——HttpRequest子模块,HttpResponse子模块
网络·网络协议·http
odoo中国2 小时前
Odoo 19 模块结构概述
开发语言·python·module·odoo·核心组件·py文件按
Uncertainty!!2 小时前
Linux多用户情况下个别用户输入密码后黑屏
linux·远程连接
necessary6532 小时前
使用Clion查看linux环境中的PG源码
linux·运维·服务器