总结别人的结果,sigchld信号处理,和close与shutdown的区别

本文探讨了如何通过SIGCHLD信号处理避免僵尸进程的产生,并介绍了close()和shutdown()函数在结束数据传输中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.      sigchld信号处理

进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。

其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。

2.      close()和shutdown()——结束数据传输
  当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:close(sockfd);
  你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
  int shutdown(intsockfd,int how);
  Sockfd的含义是显而易见的,而参数 how可以设为下列值:
  ·0-------不允许继续接收数据
  ·1-------不允许继续发送数据
  ·2-------不允许继续发送和接收数据,均为允许则调用close ()
  shutdown在操作成功时返回0,在出现错误时返回-1(并置相应errno)

按照上面的debug机制,将下面两个程序也运用进去,程序1: /* !Copyright(c) 2022-2025 TP-LINK Technologies CO.LTD. * All rights reserved. * * \file server_process.c * \brief This source file is for testing code. * * \author zhaoyunlong <zhaoyunlong1@tp-link.com.hk> * \version 1.0.0 * \date 07/8/2025 * * \history \arg 1.0.0, 07/8/2025, zhaoyunlong, Create file. */ #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> #include "http_parser.h" #include "http_response.h" #include "utils.h" #define PORT 80 /* 服务器监听端口 */ #define BACKLOG 128 /* 等待连接队伍的最大长度 */ #define BUFFER_SIZE 8192 /* 接收http请求的缓冲区大小(8KB) */ // 客户端处理函数 void handle_client(void *arg) { int client_fd = *(int *)arg; char buffer[BUFFER_SIZE]; char client_ip[INET_ADDRSTRLEN]; // 获取客户端IP struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len); /* 获取套接字关联的远程协议地址 */ inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); /* 将数值格式的IP地址转换为点分十进制格式 */ // 接收HTTP请求 ssize_t bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); /* 等待接收数据 */ if (bytes_read <= 0) { close(client_fd); free(arg); return; } buffer[bytes_read] = '\0'; // 解析HTTP请求 http_request_t req; if (parse_http_request(buffer, &req) != 0) { log_request(client_ip, "UNKNOWN", "INVALID", 400); send_error_response(client_fd, 400, "Bad Request"); close(client_fd); free(arg); return; } // 记录请求日志 log_request(client_ip, req.method, req.path, 200); // 处理请求 if ((strcmp(req.method, "GET") == 0) || (strcmp(req.method, "POST") == 0)) { handle_http_request(client_fd, &req); } else { send_error_response(client_fd, 501, "Not Implemented"); } close(client_fd); free(arg); } // SIGCHLD信号处理函数(回收僵尸进程) void sigchld_handler(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0) ; } int main() { int server_fd; int client_fd; struct sockaddr_in server_addr; // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置端口复用 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, BACKLOG) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } // 设置SIGCHLD信号处理僵尸进程 signal(SIGCHLD, sigchld_handler); // 安装信号处理器 printf("Web server running on port %d\n", PORT); while (1) { // 接受新连接 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd < 0) { perror("accept failed"); continue; } // 分配客户端套接字内存 int *client_ptr = malloc(sizeof(int)); if (!client_ptr) { perror("malloc failed"); close(client_fd); continue; } *client_ptr = client_fd; /* 多进程处理 */ // 创建子进程处理连接 pid_t pid = fork(); if (pid == 0) { // 子进程 close(server_fd); // 关闭监听套接字 handle_client(client_ptr); exit(EXIT_SUCCESS); // 处理完成后退出 } else if (pid > 0) { // 父进程 close(client_fd); // 关闭客户端套接字(父进程不需要) } else { perror("fork failed"); close(client_fd); } } // 清理资源 close(server_fd); return 0; } 程序2: /* !Copyright(c) 2022-2025 TP-LINK Technologies CO.LTD. * All rights reserved. * * \file server_epoll.c * \brief This source file is for testing code. * * \author zhaoyunlong <zhaoyunlong1@tp-link.com.hk> * \version 1.0.0 * \date 07/8/2025 * * \history \arg 1.0.0, 07/8/2025, zhaoyunlong, Create file. */ #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 <sys/epoll.h> #include <fcntl.h> #include <errno.h> #include "http_parser.h" #include "http_response.h" #include "utils.h" #define PORT 80 /* 服务器监听端口 */ #define BACKLOG 128 /* 等待连接队伍的最大长度 */ #define MAX_EVENTS 1024 /* epoll最大事件数 */ #define BUFFER_SIZE 8192 /* 接收http请求的缓冲区大小(8KB) */ // 客户端连接信息结构体 typedef struct { int fd; // 客户端文件描述符 char buffer[BUFFER_SIZE]; // 接收缓冲区 ssize_t bytes_received; // 已接收字节数 char client_ip[INET_ADDRSTRLEN]; // 客户端IP地址 } client_info_t; // 处理客户端请求 void process_client(int client_fd, client_info_t *client) { http_request_t req; // 解析HTTP请求 if (parse_http_request(client->buffer, &req) != 0) { // 解析失败记录错误日志 log_request(client->client_ip, "UNKNOWN", "INVALID", 400); send_error_response(client_fd, 400, "Bad Request"); return; } // 记录成功请求日志 log_request(client->client_ip, req.method, req.path, 200); // 处理GET/POST请求 if (strcmp(req.method, "GET") == 0 || strcmp(req.method, "POST") == 0) { handle_http_request(client_fd, &req); } else { // 不支持的HTTP方法 send_error_response(client_fd, 501, "Not Implemented"); } } int main() { int server_fd; // int client_fd; int epoll_fd; struct sockaddr_in server_addr; struct epoll_event ev, events[MAX_EVENTS]; // epoll事件结构 // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置端口复用 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, BACKLOG) < 0) { perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); } // 创建epoll实例 if ((epoll_fd = epoll_create1(0)) < 0) { perror("epoll_create1 failed"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器套接字到epoll监听 ev.events = EPOLLIN; // 监听可读事件 ev.data.fd = server_fd; // 关联服务器文件描述符 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) { perror("epoll_ctl server_fd failed"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } printf("Web server running on port %d\n", PORT); while (1) { // 等待事件发生,-1表示无限等待 int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait failed"); continue; } // 处理所有就绪事件 for (int i = 0; i < nfds; i++) { // 处理新客户端连接 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // 接受新连接 int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd < 0) { perror("accept failed"); continue; } // 设置非阻塞模式(边缘触发必须) int flags = fcntl(client_fd, F_GETFL, 0); fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); // 创建并初始化客户端信息结构 client_info_t *client = malloc(sizeof(client_info_t)); if (!client) { perror("malloc client_info failed"); close(client_fd); continue; } memset(client, 0, sizeof(client_info_t)); client->fd = client_fd; // 获取客户端IP inet_ntop(AF_INET, &client_addr.sin_addr, client->client_ip, INET_ADDRSTRLEN); // 添加客户端到epoll监听(边缘触发模式) ev.events = EPOLLIN | EPOLLET; // 边缘触发模式 ev.data.ptr = client; // 关联客户端数据结构 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) { perror("epoll_ctl client_fd failed"); close(client_fd); free(client); } } // 处理客户端数据可读事件 else if (events[i].events & EPOLLIN) { client_info_t *client = (client_info_t *)events[i].data.ptr; // 非阻塞读取数据 ssize_t count = recv(client->fd, client->buffer + client->bytes_received, BUFFER_SIZE - client->bytes_received - 1, 0); if (count > 0) { // 更新已接收字节数 client->bytes_received += count; client->buffer[client->bytes_received] = '\0'; // 确保字符串结束 // 检查HTTP请求是否结束(通过空行判断) if (strstr(client->buffer, "\r\n\r\n") != NULL) { // 移除epoll监听 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL); // 处理完整请求 process_client(client->fd, client); // 关闭连接并释放资源 close(client->fd); free(client); } } // 处理连接关闭或错误 else if (count == 0 || (count < 0 && errno != EAGAIN)) { // 移除epoll监听 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL); close(client->fd); free(client); } // EAGAIN错误在边缘触发模式下是正常的,表示数据已读完 } } } // 清理资源 close(epoll_fd); close(server_fd); return 0; }
最新发布
08-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值