当前位置: 首页 > news >正文

C语言多进程TCP服务器与客户端

一、多进程TCP服务器的创建

        示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024// 僵尸进程处理
void zombie_handler(int sig) {while (waitpid(-1, NULL, WNOHANG) > 0);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 创建TCP套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置SO_REUSEADDRint opt = 1;if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {perror("setsockopt failed");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定套接字if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 开始监听if (listen(server_fd, MAX_CLIENTS) < 0) {perror("listen failed");exit(EXIT_FAILURE);}printf("Server listening on port %d\n", PORT);// 设置僵尸进程处理器struct sigaction sa;sa.sa_handler = zombie_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction failed");exit(EXIT_FAILURE);}while (1) {// 接受新连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept failed");continue;}printf("New connection from %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));// 创建子进程处理连接pid_t pid = fork();if (pid < 0) {perror("fork failed");close(new_socket);continue;}if (pid == 0) { // 子进程close(server_fd); // 关闭不需要的监听套接字// 处理客户端请求while (1) {memset(buffer, 0, BUFFER_SIZE);ssize_t bytes_read = read(new_socket, buffer, BUFFER_SIZE - 1);if (bytes_read <= 0) {if (bytes_read == 0) printf("Client disconnected\n");elseperror("read error");break;}printf("Received: %s", buffer);// 处理请求(示例:回显)char *response = "HTTP/1.1 200 OK\r\n""Content-Type: text/plain\r\n""Connection: close\r\n""\r\n""Hello from server!";send(new_socket, response, strlen(response), 0);}close(new_socket);exit(EXIT_SUCCESS);} else { // 父进程close(new_socket); // 关闭不需要的客户端套接字}}return 0;
}

二、多进程服务器相关知识

  1. 进程管理

    1. fork() 创建子进程

      #include <unistd.h>pid_t fork(void);
      /*成功返回进程 ID, 失败返回 -1。
      */

       僵尸进程

#include <stdio.h>
#include <unistd.h>int main()
{pid_t mypid = fork();//mypid 返回值为子进程pidif(mypid == 0) // pid  == 0 表示子进程printf("I am child process\n");else //父进程{printf("Child process ID is %d\n", mypid);	sleep(30);}if(mypid == 0)puts("END Child process");elseputs("end parent process");return 0;
}/* 
此子进程为僵尸进程。僵尸进程:子进程执行完毕,但父进程未调用wait()函数或者waitpid()函数获取子进程的终止状态。
此函数中,if(pid == 0)时,执行的时子进程,else代表父进程
打印mypid的值即为子进程的进程ID。
*/

执行情况:

        将函数执行起来之后,可以看见子进程以及执行完毕,但是由于父进程未调用wait函数或者waitpid函数,故子进程成为僵尸进程,在父进程执行期间一直存在,如上图在Linux系统内ps 查看信息所展示,父进程ID为2252,子进程ID为2253,在父进程存续期间,子进程成为僵尸进程,也一直存在。

  SIGCHLD 信号处理僵尸进程

#include <signal.h>void (*signal(int signo, void(*func)(int)))(int);
/*在产生信号时调用,返回之前注册的函数指针参数为int 类型,返回void 类型的函数指针
signo常用参数:{SIGINT (2) - 中断信号 (Ctrl+C)SIGSEGV (11) - 段错误 (无效内存访问)SIGTERM (15) - 终止信号 (kill默认)SIGCHLD (17) - 子进程状态改变SIGALRM (14) - 定时器到期}
*/

使用示例:(注册信号和处理函数)

signal(SIGCHLD, myfunc);
//子进程结束信号产生则调用myfunc函数。

        alarm()函数

#include <unistd.h>unsigned int alarm(unsigned int seconds);
//返回0,或以秒为单位的距SIGALRM信号发生所剩时间。

        如果调用该函数的同时传递一个正整形参数,相应时间后(以秒为单位)将产生SIGALRM信号,若向该函数传递 0,则之前对SIGALRM信号的预约将取消。如果通过该函数预约信号后未指定该信号对应的处理函数,则(通过调用signal函数)终止进程,不做任何处理。注意!!

示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>void time_out(int sig)//alarm函数时间到达处理函数
{if(sig == SIGALRM){printf("time_out\n");}alarm(2);
}void func(int sig) //ctrl+c 信号处理函数
{if(sig == SIGINT)puts("CTRL + C is prossed");
}
int main()
{int i;signal(SIGALRM, time_out);signal(SIGINT, func);alarm(2);for(i = 0;i<3;i++) //每隔50s输出一次,理论程序会执行150s。{puts("loading>>>>");sleep(50);		}return 0;
}

输出效果:

        可见,上述程序实际运行时间不到十秒,如果按下ctrl+c则更快结束。这是因为“发生信号时将唤醒由于调用sleep函数而进入阻塞状态的进程。”

        即:如果程序自然执行,不输入ctrl+c,则程序每两秒产生SIGALR信号,同时唤醒sleep进程,即退出sleep状态,主程序中for循环执行了上次,程序三次进入sleep,同时没两秒也被唤醒。所以每2s被唤醒的时候也会退出sleep,所以程序只会输出如图第一种结果。按下ctrl+c时也会产生信号SIGINT信号唤醒sleep的进程,所以也只会输出三次。

        总的来说,程序有三次循环,而每次信号的产生都会打断sleep状态,唤醒进程,所以程序只能被信号唤醒上次,也只会执行三次信号处理函数。 

        sigaction()函数

        此处介绍一个sigaction函数,它类似于signal函数,且完全可以替换signal函数,且更稳定。

“sigaction函数在UNIX系列的不同操作系统中完全相同,而signal函数可能存在区别”。

#include <signal.h>int sigaction(int signo, const struct sigaction *act, struct sigaction*oldact);
//成功返回 0, 失败时返回-1.
/*signo: 与signal函数相同,传递信号信息act:对应于第一个参数的信号处理函数信息oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递 0
*/struct sigaction
{void (*sa_handler)(int);sigset_t sa_mask;int sa_flags;
};
/*sa_mask和sa_flags的所有位均初始化为 0 即可,这两个成员用于指定信号相关的 选项 和 特性,而我们的目的主要是防止产生僵尸进程,故省略。sa_handler:保存信号处理函数的指针值(地址值)。
*/

        则上面的函数可修改为:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>void time_out(int sig)//alarm函数时间到达处理函数
{if(sig == SIGALRM){printf("time_out\n");}alarm(2);
}void func(int sig) //ctrl+c 信号处理函数
{if(sig == SIGINT)puts("CTRL + C is prossed");
}
int main()
{int i;struct sigaction act;act.sa_handler = time_out;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGALRM, &act, 0);alarm(2);struct sigaction act_pro;act_pro.sa_handler = func;sigemptyset(&act_pro.sa_mask);act_pro.sa_flags = 0;sigaction(SIGINT, &act_pro, 0);for(i = 0;i<3;i++) //每隔50s输出一次,理论程序会执行150s。{puts("loading>>>>");sleep(50);		}return 0;
}

        执行情况如下:

 

套接字管理

// 父子进程资源分离
if (pid == 0) { close(server_fd); // 子进程关闭监听套接字
} else {close(new_socket); // 父进程关闭客户端套接字
}

多进程TCP客户端

完整客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define NUM_CLIENTS 3
#define BUFFER_SIZE 1024void client_process(int client_id) {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};// 创建套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERVER_PORT);// 转换IP地址if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("invalid address");exit(EXIT_FAILURE);}// 连接服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("connection failed");exit(EXIT_FAILURE);}printf("Client %d connected to server\n", client_id);// 发送请求char message[BUFFER_SIZE];snprintf(message, sizeof(message), "GET / HTTP/1.1\r\n""Host: localhost\r\n""User-Agent: Client/%d\r\n""\r\n", client_id);send(sock, message, strlen(message), 0);printf("Client %d sent request\n", client_id);// 接收响应ssize_t bytes_read;while ((bytes_read = read(sock, buffer, BUFFER_SIZE - 1)) > 0) {buffer[bytes_read] = '\0';printf("Client %d received:\n%s\n", client_id, buffer);}close(sock);printf("Client %d disconnected\n", client_id);exit(EXIT_SUCCESS);
}int main() {pid_t pids[NUM_CLIENTS];// 创建多个客户端进程for (int i = 0; i < NUM_CLIENTS; i++) {pids[i] = fork();if (pids[i] < 0) {perror("fork failed");exit(EXIT_FAILURE);}if (pids[i] == 0) { // 子进程client_process(i + 1);}}// 父进程等待所有子进程结束for (int i = 0; i < NUM_CLIENTS; i++) {waitpid(pids[i], NULL, 0);}printf("All clients completed\n");return 0;
}

多进程客户端关键技术

  1. 并发连接
    for (int i = 0; i < NUM_CLIENTS; i++) {pids[i] = fork();if (pids[i] == 0) {client_process(i + 1);}
    }
  2. 请求定制
    snprintf(message, sizeof(message), "GET / HTTP/1.1\r\n""Host: localhost\r\n""User-Agent: Client/%d\r\n""\r\n", client_id);
  3. 响应处理
    while ((bytes_read = read(sock, buffer, BUFFER_SIZE - 1)) > 0) {buffer[bytes_read] = '\0';printf("Client %d received:\n%s\n", client_id, buffer);
    }

系统测试与优化

测试方法

# 编译服务器
gcc server.c -o server# 编译客户端
gcc client.c -o client# 启动服务器
./server# 在另一个终端启动客户端
./client

性能优化技术

  1. 进程池技术
    #define POOL_SIZE 5// 预先创建进程
    for (int i = 0; i < POOL_SIZE; i++) {pid_t pid = fork();if (pid == 0) {worker_process(); // 工作进程循环处理请求}
    }
  2. 连接复用
    // 保持连接而非每次新建
    while (1) {// 处理多个请求process_request(socket);
    }
  3. 负载监控
    void monitor_load() {struct rusage usage;getrusage(RUSAGE_SELF, &usage);printf("CPU usage: %ld.%06ld sec\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
    }

安全增强

  1. 权限降级
    if (setuid(getuid()) < 0) {perror("setuid failed");exit(EXIT_FAILURE);
    }
  2. 资源限制
    #include <sys/resource.h>struct rlimit limit = {.rlim_cur = 100, // 100个文件描述符.rlim_max = 100
    };
    setrlimit(RLIMIT_NOFILE, &limit);
  3. 输入验证
    // 验证接收的数据
    if (strstr(buffer, "malicious") != NULL) {close(socket);return;
    }

应用场景与扩展

适用场景

  1. 高并发网络服务(HTTP服务器)
  2. 并行数据处理系统
  3. 实时通信应用
  4. 分布式计算节点
  5. 压力测试工具

扩展方向

  1. 添加SSL/TLS加密

    #include <openssl/ssl.h>SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, socket);
    SSL_accept(ssl);
    SSL_read(ssl, buffer, sizeof(buffer));
  2. 实现进程间通信

    // 使用管道
    int pipefd[2];
    pipe(pipefd);
    write(pipefd[1], data, size);
  3. 添加日志系统

    void log_message(const char *msg) {FILE *log = fopen("server.log", "a");fprintf(log, "[%ld] %s\n", time(NULL), msg);fclose(log);
    }
  4. 配置热重载

    // 使用SIGHUP信号
    signal(SIGHUP, reload_config);

总结对比

特性多进程服务器多进程客户端
主要目的处理并发连接模拟并发请求
进程角色父进程管理,子进程处理父进程协调,子进程执行
资源消耗较高(每个连接一个进程)可控(可配置进程数)
适用场景长期运行的服务测试/批量任务
复杂度高(需处理僵尸进程)中(较简单)

多进程TCP服务器与客户端的实现展示了C语言在系统编程中的强大能力。通过合理运用进程管理、套接字编程和并发控制技术,可以构建出高性能的网络应用。关键点包括:

  1. 正确的进程管理:处理僵尸进程,避免资源泄露
  2. 高效的资源分配:及时关闭不需要的文件描述符
  3. 健壮的错误处理:应对各种网络异常
  4. 可扩展的架构:支持进程池等优化技术

这种模式虽然资源消耗大于线程模型,但在稳定性、安全性和隔离性方面具有优势,特别适合需要高可靠性的服务端应用。

相关文章:

  • I/O模式之epoll,本文会讲到epoll的相关接口以及底层,还会涉及水平和边缘工作模式,以及通过epoll相关接口实现一个水平工作模式服务端
  • @Profile, @Conditional, @ConditionalOnMissingBean, @ConditionalOnClass
  • 七大技术路线解析:自动驾驶如何被数据重新定义
  • 在python中获取符合特定模式的文件
  • 【互联网基础】互联网公司机房怎么设计
  • kylin 10 安装 redis-7.4.4
  • OpenBayes 一周速览丨对标GPT-4o! BAGEL统一处理多模态数据理解和生成任务; 专为软件工程任务设计, Devstral自主处理复杂工程问题
  • 从入门到精通:C# 中 AutoMapper 的深度解析与实战应用
  • 双向链表——(有头双向循环链表)
  • 2025秋招后端突围:JVM核心面试题与高频考点深度解析
  • 个人支出智能分析系统
  • Cursor-1.0安装Jupyter-Notebook,可视化运行.ipynb文件中Python分片代码
  • OpenCV CUDA模块图像变形------对图像进行GPU加速的透视变换函数warpPerspective()
  • vscode 无法连接到ssh
  • 使用 Spring Boot 和 dynamic-datasource 实现多数据源集成
  • Python小工具开发实战:从零构建自动化文件管理器的心得与体悟
  • Python-PLAXIS自动化建模技术与典型岩土工程
  • 应用探析|千眼狼高速摄像机、sCMOS相机、DIC测量、PIV测量在光学领域的应用
  • 基于C#+SQLServer2016实现(控制台)小型机票订票系统
  • 【Bluedroid】蓝牙启动之 GAP_Init 流程源码解析
  • 网站网站地图怎么做/免费发布信息网网站
  • 网站建设行业分析/最近几天新闻大事
  • 网站建设如何财务处理/seo包括哪些方面
  • 如何做求婚网站/电脑系统优化软件排行榜
  • 做网站建设的联系电话/什么是网站推广优化
  • 深圳做营销网站公司简介/有哪些可以免费推广的平台