为什么 while((pid = waitpid(-1, &stat, WNOHANG)) > 0)能处理所有子进程

本文详细解析了SIGCHLD信号处理程序及其与waitpid函数的关联,阐述了如何通过while循环配合waitpid处理多个子进程的退出,确保父进程能够回收所有僵尸子进程,即使在信号丢失的情况下。

在unp中有一节代码是这样的:

void sig_chld(int signo)
{
       pid_t   pid;
       int     stat;
       
       while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
               printf("child %d terminated\n", pid);
       }
        return;
}

见unp第110页。

是什么?

它是一个SIGCHLD信号处理程序。当父进程的一个子进程结束时,会给父进程发送一个SIGCHLD信号。

核心在哪儿?

当父进程的5个子进程同时向父进程发送SIGCHILD时会发生什么?

父进程只会调用一次信号处理程序,并丢弃其余四个信号。虽然只接受了一个SIGCHLD信号,但是

while((pid = waitpid(-1, &stat, WNOHANG)) > 0)

这条语句会处理所有的僵尸子进程。这就意味着,即使丢失了一部分SIGCHLD信号,父进程也能够回收所有的子进程。

为什么?

事实上,waitpid与SIGCHLD信号的关联并不大。waitpid的函数构造是,回收第一个僵尸子进程。而加上while之后就能够回收所有在while运行期间内结束的子进程。与SIGCHLD关系不大。

 

#define _XOPEN_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <arpa/inet.h> #include <sys/socket.h> #include <signal.h> #include <fcntl.h> #include <errno.h> #include <sys/wait.h> #include "libCommon.h" #include <asm-generic/signal.h> #define PORT 8080 /* 服务器监听端口 */ #define BUFFER_SIZE 4096 /* 缓冲区大小 */ #define WEB_ROOT "./web" /* 网站根目录 */ /** * @brief 处理客户端请求 * * 从客户端套接字读取请求,解析请求方法和路径,根据路径构建本地文件路径,检查文件是否存在, * 并根据文件的存在性返回相应的HTTP响应。 * * @param client_socket 客户端套接字描述符 */ void handle_client(int client_socket) { char buffer[BUFFER_SIZE]; ssize_t bytes_read = read(client_socket, buffer, sizeof(buffer)); if (bytes_read < 0) { perror("Error reading from client"); close(client_socket); return; } char *saveptr; char *method = strtok_r(buffer, " ",saveptr); char *path = strtok_r(NULL, " ",&saveptr); if (method == NULL || path == NULL) { perror("Invalid request"); close(client_socket); return; } /* 构建本地文件路径 */ char file_path[BUFFER_SIZE]; if (strcmp(path, "/") == 0) { snprintf(file_path, sizeof(file_path), "%s/Index.html", WEB_ROOT); /* 默认首页 */ } else { snprintf(file_path, sizeof(file_path), "%s%s", WEB_ROOT, path); } printf("File_Path:%s\n", file_path); /* 检查文件是否存在 */ struct stat file_stat; if (stat(file_path, &file_stat) == -1 || S_ISDIR(file_stat.st_mode)) { char path[BUFFER_SIZE]; getcwd(path, sizeof(path)); printf("%s\n", path); printf("path:%d, access:%d ,stat:%d, s_isdir:%d\r\n", access(file_path, F_OK), access(file_path, R_OK / W_OK), stat(file_path, &file_stat) == -1, S_ISDIR(file_stat.st_mode)); char response[] = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html\r\n" "Content-Length: %ld\r\n\r\n" "<html><body><h1>404 Not Found</h1></body></html>\r\n"; write(client_socket, response, strlen(response)); printf("回答是:%s", response); } else { /* 读取文件内容并发送 */ int file_fd = open(file_path, O_RDONLY); if (file_fd == -1) { perror("打开文件失败"); close(client_socket); return NULL; } char response[BUFFER_SIZE]; char content_type[20] = {0}; getContentType(file_path, content_type); snprintf(response, sizeof(response), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n\r\n", content_type, file_stat.st_size); write(client_socket, response, strlen(response)); printf("回答是:%s", response); char file_buffer[BUFFER_SIZE]; ssize_t bytes_written; while ((bytes_written = read(file_fd, file_buffer, BUFFER_SIZE)) > 0) { write(client_socket, file_buffer, bytes_written); } close(file_fd); } close(client_socket); exit(EXIT_SUCCESS); // 子进程结束 } /** * @brief 处理 SIGCHLD 信号 * * 当子进程终止时,操作系统会发送 SIGCHLD 信号给其父进程。 * 该信号处理程序使用 waitpid() 函数清理所有已经终止的子进程, * 以避免僵尸进程的产生。 * * @param signum 信号编号,这里为 SIGCHLD */ void sigchld_handler(int signum) { while (waitpid(-1, NULL, WNOHANG) > 0) ; } int main(void) { /* 创建服务器 */ int server_socket = socket(AF_INET, SOCK_STREAM, 0); /* TCP套接字*/ if (server_socket == -1) { perror("创建套接字失败"); exit(EXIT_FAILURE); } /* 配置服务器地址 */ struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; /* IPv4地址 */ server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; /* 监听所有地址 */ /* 绑定地址 */ if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("绑定套接字失败"); close(server_socket); exit(EXIT_FAILURE); } /* 开始监听 */ if (listen(server_socket, 15) == -1) { perror("监听套接字失败"); close(server_socket); exit(EXIT_FAILURE); } // 设置SIGCHLD信号处理程序 struct sigaction sa; sa.sa_handler = sigchld_handler; // 指定处理函数 sigemptyset(&sa.sa_mask); // 不阻塞其他信号 sa.sa_flags = SA_RESTART; // 系统调用被中断时自动重启 if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("设置SIGCHLD处理程序失败"); exit(EXIT_FAILURE); } printf("服务器启动成功,监听端口 %d\n", PORT); // 循环接受客户端连接并处理请求 while (1) { struct sockaddr_in client_addr; socklen_t client_addr_size = sizeof(client_addr); int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_size); if (client_socket == -1) { perror("接受客户端连接失败"); continue; } pid_t pid = fork(); // 创建子进程处理请求 if (pid < 0) { perror("fork() error"); close( client_socket); continue; } else if (pid == 0) { // 子进程处理请求 close(server_socket); // 关闭服务器套接字,避免竞争条件 handle_client(client_socket); exit(EXIT_SUCCESS); // 子进程结束 } else { // 父进程继续接受其他客户端连接 close(client_socket); } } close(server_socket); return 0; } 修正这个多进程实现的webserver,给出修正后的完整代码
08-17
现在来整理一下这道题,题目要求是: 实现一个简单的类似 Shell 的程序(下面称为简易 Shell,要求如下: 1.可以在程序中输入命令(要求编写的另一个程序)的路径并在子进程中执行 2.在调用命令时可以将参数传给命令 3.可以从命令中输入内容,发送给简单 Shell 接收,并打印出来。 4.如果简易 Shell 收到的内容为 stop,则 Shell 将命令进程关闭。 5.直接在简易 Shell 中输入 exit,简易 Shell 自己退出 我写的main.c文件如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <errno.h> #define MAX_INPUT 1024 /* max input legth */ #define MAX_ARGS 64 /* max number of arguments */ #define MAX_PATH 1024 /* max path legth */ int main() { char input[MAX_INPUT] = {0}; /* Input buffer */ char *args[MAX_ARGS] = {NULL}; /* Argument array */ char current_dir[MAX_PATH] = {0}; /* Current working directory */ pid_t pid = 0; int pipe_fd[2] = {0}; /* Pipe */ /* Get current working directory */ if (getcwd(current_dir, MAX_PATH) == NULL) { perror("getcwd failed"); exit(EXIT_FAILURE); } while (1) { /* Display current directory(green) */ printf("\033[1;32m%s\033[0m$ ", current_dir); fflush(stdout); /* Read input */ if (fgets(input, MAX_INPUT, stdin) == NULL) { break; } /* Remove \n */ input[strcspn(input, "\n")] = '\0'; /* Skip empty input*/ if (strlen(input) == 0) { continue; } /* Exit command */ if (strcmp(input, "exit") == 0) { printf("Exit command received. Shutting down.\n"); break; } /* Read command */ int i = 0; char *token = strtok(input, " "); while (token != NULL && i < MAX_ARGS - 1) { args[i++] = token; token = strtok(NULL, " "); } args[i] = NULL; /* cd */ if (strcmp(args[0], "cd") == 0) { char *path = (i > 1) ? args[1] : getenv("HOME"); if (chdir(path) == -1) { perror("chdir failed"); } else { /* Update current directory */ if (getcwd(current_dir, MAX_PATH) == NULL) { perror("getcwd failed"); } } continue; } /* Create pipe */ if (pipe(pipe_fd) == -1) { perror("pipe failed"); continue; } /* Create child process */ pid = fork(); if (pid < 0) { perror("fork failed"); close(pipe_fd[0]); close(pipe_fd[1]); continue; } if (pid == 0) { /* Child */ /* Close read end */ close(pipe_fd[0]); /* redirect to write end */ dup2(pipe_fd[1], STDOUT_FILENO); close(pipe_fd[1]); printf("Executing command: %s\n", args[0]); execvp(args[0], args); /* Fail */ perror("execvp failed"); exit(EXIT_FAILURE); } else { /* Father process */ /* Close write end */ close(pipe_fd[1]); char buffer[MAX_INPUT]; ssize_t bytes_read; int stop_detected = 0; /* Read output */ while ((bytes_read = read(pipe_fd[0], buffer, MAX_INPUT - 1)) > 0) { buffer[bytes_read] = '\0'; printf("%s", buffer); fflush(stdout); /* Check stop */ if (strstr(buffer, "stop") != NULL) { stop_detected = 1; break; } } /* Close rean end */ close(pipe_fd[0]); if (stop_detected) { if (kill(pid, SIGTERM) == -1 && errno != ESRCH) { perror("kill failed"); } printf("Command terminated by 'stop' signal\n"); } waitpid(pid, NULL, 0); } memset(args, 0, sizeof(args)); } return 0; } 请结合要求和代码,帮我写一份报告文档,详解说明思路/步骤、测试结果
08-07
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #define MAX_INPUT 1024 #define MAX_ARGS 64 // 全局变量记录后台进程PID pid_t background_pid = -1; void execute_command(char **args, int background) { pid_t pid = fork(); if (pid == 0) { // 子进程执行区域 printf("我是子进程;pid:%d, ppid:%d, pid=%d \n",getpid(),getppid(),pid); sleep(1); execvp(args[0], args); //在子进程中使用execvp允许在不终止父进程的情况下运行新程序。父进程可以继续执行,同时子进程执行不同的任务。 perror("execvp failed"); exit(EXIT_FAILURE); } else if (pid > 0) { // 父进程执行区域 if (!background) { waitpid(pid, NULL, 0); // 等待前台进程结束 } else { background_pid = pid; // 记录后台进程PID printf("[%d] running in background\n", pid); } printf("我是父进程;pid:%d, ppid:%d, pid=%d \n",getpid(),getppid(),pid); sleep(1); } else { perror("fork failed"); } } int main() { char input[MAX_INPUT]; char *args[MAX_ARGS]; char *token; int background; while (1) { printf("mysh> "); fflush(stdout); // 读取用户输入 if (!fgets(input, MAX_INPUT, stdin)) break; // 处理换行符 input[strcspn(input, "\n")] = '\0'; // 解析输入 int i = 0; token = strtok(input, " "); while (token != NULL && i < MAX_ARGS - 1) { args[i++] = token; token = strtok(NULL, " "); } args[i] = NULL; // 空命令处理 if (i == 0) continue; // 检查后台运行标识 (&) background = 0; if (i > 0 && strcmp(args[i-1], "&") == 0) { background = 1; args[i-1] = NULL; } if (strcmp(args[0], "exit") == 0) { break; } else if (strcmp(args[0], "stop") == 0) { if (background_pid != -1) { kill(background_pid, SIGTERM); printf("Terminated process [%d]\n", background_pid); background_pid = -1; } else { printf("No background process running\n"); } } else { // 执行外部命令 execute_command(args, background); } } return EXIT_SUCCESS; } 上面是一个简易shell,main函数中的stop 模块的作用是什么?怎么验证该功能/
08-08
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <time.h> #include <errno.h> #include <ctype.h> #include <signal.h> #include <arpa/inet.h> #include <sys/wait.h> #define PORT 80 #define MAX_CLIENTS 100 #define BUFFER_SIZE 8192 #define ROOT_DIR "" int debug_mode = 1; // 调试模式开关 pid_t main_pid; // 日志函数 void log_message(const char *type, const char *message, int level) { if (level > 0 && !debug_mode) return; time_t now = time(NULL); char *time_str = ctime(&now); time_str[strlen(time_str) - 1] = '\0'; char log_buffer[1024]; snprintf(log_buffer, sizeof(log_buffer), "[%s] %s: %s", time_str, type, message); printf("%s\n", log_buffer); // 写入日志文件(使用O_APPEND保证多进程写入安全) int log_fd = open("webserver.log", O_WRONLY | O_CREAT | O_APPEND, 0644); if (log_fd >= 0) { dprintf(log_fd, "%s\n", log_buffer); close(log_fd); } } // 获取绝对路径 void get_absolute_path(char *abs_path, const char *rel_path) { char cwd[1024]; if (getcwd(cwd, sizeof(cwd))) { snprintf(abs_path, 1024, "%s/%s/%s", cwd, ROOT_DIR, rel_path); } else { snprintf(abs_path, 1024, "%s/%s", ROOT_DIR, rel_path); } } // 路径安全检查和规范化 int normalize_path(char *path) { // 移除多余的斜杠 char *src = path; char *dst = path; while (*src) { if (*src == '/' && *(src + 1) == '/') { src++; continue; } *dst++ = *src++; } *dst = '\0'; // 检查路径遍历攻击 if (strstr(path, "..") != NULL) { return 0; } return 1; } // 获取文件类型 const char *get_content_type(const char *path) { const char *ext = strrchr(path, '.'); if (ext) { if (strcmp(ext, ".html") == 0) return "text/html"; if (strcmp(ext, ".htm") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".json") == 0) return "application/json"; if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg"; if (strcmp(ext, ".png") == 0) return "image/png"; if (strcmp(ext, ".gif") == 0) return "image/gif"; if (strcmp(ext, ".ico") == 0) return "image/x-icon"; if (strcmp(ext, ".svg") == 0) return "image/svg+xml"; if (strcmp(ext, ".txt") == 0) return "text/plain"; if (strcmp(ext, ".pdf") == 0) return "application/pdf"; } return "application/octet-stream"; } // 发送HTTP响应 void send_response(int client_fd, int status, const char *status_text, const char *content_type, const char *body, size_t body_len) { char header[512]; int header_len = snprintf(header, sizeof(header), "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "Connection: close\r\n\r\n", status, status_text, content_type, body_len); write(client_fd, header, header_len); if (body && body_len > 0) { write(client_fd, body, body_len); } } // 发送文件 void send_file(int client_fd, const char *path) { char abs_path[1024]; get_absolute_path(abs_path, path); if (!normalize_path(abs_path)) { const char *forbidden = "403 Forbidden"; send_response(client_fd, 403, "Forbidden", "text/plain", forbidden, strlen(forbidden)); log_message("SECURITY", "Path traversal attempt detected", 0); return; } char debug_msg[1024]; snprintf(debug_msg, sizeof(debug_msg), "Accessing file: %s", abs_path); log_message("DEBUG", debug_msg, 1); FILE *file = fopen(abs_path, "rb"); if (!file) { char error_msg[512]; snprintf(error_msg, sizeof(error_msg), "Failed to open %s: %s", abs_path, strerror(errno)); log_message("ERROR", error_msg, 0); const char *not_found = "404 Not Found"; send_response(client_fd, 404, "Not Found", "text/plain", not_found, strlen(not_found)); return; } fseek(file, 0, SEEK_END); long file_size = ftell(file); fseek(file, 0, SEEK_SET); if (file_size <= 0) { fclose(file); const char *server_error = "500 Internal Server Error (Empty file)"; send_response(client_fd, 500, "Internal Server Error", "text/plain", server_error, strlen(server_error)); log_message("ERROR", "File is empty", 0); return; } char *buffer = malloc(file_size); if (!buffer) { fclose(file); const char *server_error = "500 Internal Server Error"; send_response(client_fd, 500, "Internal Server Error", "text/plain", server_error, strlen(server_error)); log_message("ERROR", "Memory allocation failed", 0); return; } size_t read_size = fread(buffer, 1, file_size, file); fclose(file); if (read_size != (size_t) file_size) { free(buffer); const char *server_error = "500 Internal Server Error"; send_response(client_fd, 500, "Internal Server Error", "text/plain", server_error, strlen(server_error)); log_message("ERROR", "File read error", 0); return; } const char *content_type = get_content_type(path); send_response(client_fd, 200, "OK", content_type, buffer, file_size); free(buffer); } // 处理目录请求 void handle_directory(int client_fd, const char *path) { char index_path[1024]; snprintf(index_path, sizeof(index_path), "%s/Contact.html", path); struct stat st; if (stat(index_path, &st) == 0 && S_ISREG(st.st_mode)) { send_file(client_fd, index_path); } else { const char *not_found = "404 Directory index not found"; send_response(client_fd, 404, "Not Found", "text/plain", not_found, strlen(not_found)); log_message("ERROR", "Directory index not found", 0); } } // 处理客户端请求(现在在子进程中执行) void handle_client(int client_fd) { char buffer[BUFFER_SIZE]; ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1); if (bytes_read <= 0) { close(client_fd); return; } buffer[bytes_read] = '\0'; if (debug_mode) { char req_msg[BUFFER_SIZE + 50]; snprintf(req_msg, sizeof(req_msg), "Request:\n%.*s", (int) bytes_read, buffer); log_message("REQUEST", req_msg, 1); } char method[16], path[256], protocol[16]; if (sscanf(buffer, "%15s %255s %15s", method, path, protocol) != 3) { const char *bad_request = "400 Bad Request"; send_response(client_fd, 400, "Bad Request", "text/plain", bad_request, strlen(bad_request)); close(client_fd); return; } // 完全忽略 favicon.ico 请求 if (strcmp(path, "/favicon.ico") == 0) { close(client_fd); return; // 直接返回,不发送任何响应 } // 处理路径 char full_path[1024]; if (strcmp(path, "/") == 0) { // 默认显示 Contact.html strcpy(full_path, "Contact.html"); } else { // 跳过开头的斜杠 strcpy(full_path, path + 1); } // 获取绝对路径用于文件检查 char abs_path[1024]; get_absolute_path(abs_path, full_path); // 调试信息 char debug_msg[1024]; snprintf(debug_msg, sizeof(debug_msg), "Resolved path: %s", abs_path); log_message("DEBUG", debug_msg, 1); // 处理GET请求 if (strcmp(method, "GET") == 0) { struct stat st; if (stat(abs_path, &st) == 0) { if (S_ISDIR(st.st_mode)) { // 如果是目录,尝试查找目录下的 Contact.html handle_directory(client_fd, abs_path); } else { // 发送文件 send_file(client_fd, full_path); } } else { // 文件不存在 char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "File not found: %s", abs_path); log_message("ERROR", error_msg, 0); const char *not_found = "404 Not Found"; send_response(client_fd, 404, "Not Found", "text/plain", not_found, strlen(not_found)); } } // 处理POST请求 else if (strcmp(method, "POST") == 0) { // 查找请求体 char *body = strstr(buffer, "\r\n\r\n"); if (body) { body += 4; // 跳过空行 // 记录POST数据 char log_msg[1024]; snprintf(log_msg, sizeof(log_msg), "POST data: %.*s", (int) (bytes_read - (body - buffer)), body); log_message("INFO", log_msg, 1); // 简单的POST处理示例 const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: 12\r\n" "Connection: close\r\n\r\n" "POST Received"; write(client_fd, response, strlen(response)); } else { const char *bad_request = "400 Bad Request (Missing body)"; send_response(client_fd, 400, "Bad Request", "text/plain", bad_request, strlen(bad_request)); } } // 处理其他HTTP方法 else { const char *not_allowed = "405 Method Not Allowed"; send_response(client_fd, 405, "Method Not Allowed", "text/plain", not_allowed, strlen(not_allowed)); } close(client_fd); } // 僵尸进程回收函数 void reap_zombies(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0); } // 清理函数 void cleanup() { log_message("INFO", "Server shutting down", 0); } void handle_signal(int sig) { if (getpid() != main_pid) { _exit(0); // 子进程直接退出 } else if (sig == SIGINT || sig == SIGTERM) { log_message("INFO", "Received termination signal, shutting down", 0); exit(0); // 主进程正常退出 } } int main() { main_pid = getpid(); // 第一行获取主进程PID // 仅主进程注册清理函数 if (getpid() == main_pid) { atexit(cleanup); } // 设置信号处理 signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); signal(SIGCHLD, reap_zombies); // 添加子进程回收信号 // 创建套接字 int server_fd; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置套接字选项 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); exit(EXIT_FAILURE); } // 绑定地址 struct sockaddr_in address; 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); } // 检查根目录是否存在 char root_path[1024]; get_absolute_path(root_path, ""); struct stat st; if (stat(root_path, &st) != 0 || !S_ISDIR(st.st_mode)) { char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Root directory '%s' not found or inaccessible", root_path); log_message("FATAL", error_msg, 0); exit(EXIT_FAILURE); } else { char info_msg[256]; snprintf(info_msg, sizeof(info_msg), "Serving files from: %s", root_path); log_message("INFO", info_msg, 0); } log_message("INFO", "Server started successfully (Multi-process)", 0); printf("Multi-process server running on port %d\n", PORT); // 主循环 while (1) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &addr_len); if (client_fd < 0) { if (errno == EINTR) continue; // 信号中断处理 perror("accept failed"); continue; } // 记录客户端信息 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr . sin_addr, client_ip, INET_ADDRSTRLEN); char conn_msg[256]; snprintf(conn_msg, sizeof(conn_msg), "Client connected: %s:%d", client_ip, ntohs(client_addr . sin_port)); log_message("INFO", conn_msg, 1); // 创建子进程处理请求 pid_t pid = fork(); if (pid == 0) { // 子进程 close(server_fd); // 关闭监听套接字 handle_client(client_fd); _exit(EXIT_SUCCESS); // 处理完成后退出 } else if (pid > 0) { // 父进程 close(client_fd); // 关闭父进程中不需要的客户端套接字 } else { perror("fork failed"); close(client_fd); } } close(server_fd); return 0; } #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> #include <time.h> #include <arpa/inet.h> #define PORT 80 #define MAX_EVENTS 1024 #define BUFFER_SIZE 8192 #define ROOT_DIR "" int debug_mode = 1; // 连接状态结构体 typedef struct { int fd; char read_buf[BUFFER_SIZE]; size_t read_len; char write_buf[BUFFER_SIZE]; size_t write_len; size_t write_sent; int keep_alive; struct sockaddr_in client_addr; } connection_t; // 日志函数 void log_message(const char *type, const char *message, int level) { if (level > 0 && !debug_mode) return; time_t now = time(NULL); char *time_str = ctime(&now); time_str[strlen(time_str) - 1] = '\0'; char log_buffer[1024]; snprintf(log_buffer, sizeof(log_buffer), "[%s] %s: %s", time_str, type, message); printf("%s\n", log_buffer); // 写入日志文件(使用O_APPEND保证多进程写入安全) int log_fd = open("webserver.log", O_WRONLY | O_CREAT | O_APPEND, 0644); if (log_fd >= 0) { dprintf(log_fd, "%s\n", log_buffer); close(log_fd); } } // 获取绝对路径 void get_absolute_path(char *abs_path, const char *rel_path) { char cwd[1024]; if (getcwd(cwd, sizeof(cwd))) { snprintf(abs_path, 1024, "%s/%s/%s", cwd, ROOT_DIR, rel_path); } else { snprintf(abs_path, 1024, "%s/%s", ROOT_DIR, rel_path); } } // 路径安全检查和规范化 int normalize_path(char *path) { // 移除多余的斜杠 char *src = path; char *dst = path; while (*src) { if (*src == '/' && *(src + 1) == '/') { src++; continue; } *dst++ = *src++; } *dst = '\0'; // 检查路径遍历攻击 if (strstr(path, "..") != NULL) { return 0; } return 1; } // 获取文件类型 const char *get_content_type(const char *path) { const char *ext = strrchr(path, '.'); if (ext) { if (strcmp(ext, ".html") == 0) return "text/html"; if (strcmp(ext, ".htm") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".json") == 0) return "application/json"; if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg"; if (strcmp(ext, ".png") == 0) return "image/png"; if (strcmp(ext, ".gif") == 0) return "image/gif"; if (strcmp(ext, ".ico") == 0) return "image/x-icon"; if (strcmp(ext, ".svg") == 0) return "image/svg+xml"; if (strcmp(ext, ".txt") == 0) return "text/plain"; if (strcmp(ext, ".pdf") == 0) return "application/pdf"; } return "application/octet-stream"; } // 设置非阻塞IO void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl F_GETFL"); exit(EXIT_FAILURE); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL"); exit(EXIT_FAILURE); } } // 创建HTTP响应头 void build_http_header(char *header, int status, const char *status_text, const char *content_type, size_t content_len, int keep_alive) { snprintf(header, BUFFER_SIZE, "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "Connection: %s\r\n" "Server: epoll-server\r\n\r\n", status, status_text, content_type, content_len, keep_alive ? "keep-alive" : "close"); } // 发送文件响应 void send_file_response(connection_t *conn, const char *path) { char abs_path[1024]; get_absolute_path(abs_path, path); if (!normalize_path(abs_path)) { const char *forbidden = "403 Forbidden"; build_http_header(conn -> write_buf, 403, "Forbidden", "text/plain", strlen(forbidden), conn -> keep_alive); memcpy(conn -> write_buf + strlen(conn -> write_buf), forbidden, strlen(forbidden)); conn -> write_len = strlen(conn -> write_buf); return; } int fd = open(abs_path, O_RDONLY); if (fd == -1) { const char *not_found = "404 Not Found"; build_http_header(conn -> write_buf, 404, "Not Found", "text/plain", strlen(not_found), conn -> keep_alive); memcpy(conn -> write_buf + strlen(conn -> write_buf), not_found, strlen(not_found)); conn -> write_len = strlen(conn -> write_buf); return; } struct stat st; fstat(fd, &st); off_t file_size = st . st_size; const char *content_type = get_content_type(path); build_http_header(conn -> write_buf, 200, "OK", content_type, file_size, conn -> keep_alive); size_t header_len = strlen(conn -> write_buf); // 使用sendfile零拷贝发送文件 conn -> write_len = header_len; conn -> write_sent = 0; // 如果文件很小,直接读取到内存 if (file_size < BUFFER_SIZE - header_len) { ssize_t n = read(fd, conn -> write_buf + header_len, file_size); if (n > 0) { conn -> write_len += n; } close(fd); } else { // 大文件使用sendfile在发送时处理 close(fd); // 实际发送在事件循环中处理 } } // 处理HTTP请求 void handle_request(connection_t *conn) { // 解析请求行 char method[16], path[256], protocol[16]; if (sscanf(conn -> read_buf, "%15s %255s %15s", method, path, protocol) != 3) { const char *bad_request = "400 Bad Request"; build_http_header(conn -> write_buf, 400, "Bad Request", "text/plain", strlen(bad_request), 0); memcpy(conn -> write_buf + strlen(conn -> write_buf), bad_request, strlen(bad_request)); conn -> write_len = strlen(conn -> write_buf); return; } // 检查Keep-Alive conn -> keep_alive = (strstr(conn -> read_buf, "Connection: keep-alive") != NULL); // 忽略favicon if (strcmp(path, "/favicon.ico") == 0) { conn -> write_len = 0; return; } // 处理路径 char full_path[1024]; if (strcmp(path, "/") == 0) { strcpy(full_path, "Contact.html"); } else { strcpy(full_path, path + 1); } // 处理GET请求 if (strcmp(method, "GET") == 0) { send_file_response(conn, full_path); } // 处理POST请求 else if (strcmp(method, "POST") == 0) { const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: 12\r\n" "Connection: close\r\n\r\n" "POST Received"; strncpy(conn -> write_buf, response, strlen(response)); conn -> write_len = strlen(response); conn -> keep_alive = 0; // POST后关闭连接 } // 其他方法 else { const char *not_allowed = "405 Method Not Allowed"; build_http_header(conn -> write_buf, 405, "Method Not Allowed", "text/plain", strlen(not_allowed), 0); memcpy(conn -> write_buf + strlen(conn -> write_buf), not_allowed, strlen(not_allowed)); conn -> write_len = strlen(conn -> write_buf); } } // 初始化服务器套接字 int init_server_socket() { int server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (server_fd == -1) { perror("socket"); exit(EXIT_FAILURE); } int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, .sin_port = htons(PORT) }; if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) { perror("bind"); close(server_fd); exit(EXIT_FAILURE); } if (listen(server_fd, SOMAXCONN) < 0) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } return server_fd; } int main() { int server_fd = init_server_socket(); log_message("INFO", "IO Multiplexing server started (epoll)", 0); // 创建epoll实例 int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器套接字到epoll struct epoll_event ev; ev . events = EPOLLIN; ev . data . fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) { perror("epoll_ctl: server_fd"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 事件循环 struct epoll_event events[MAX_EVENTS]; connection_t *connections[MAX_EVENTS] = {0}; while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { // 新连接 if (events[i] . data . fd == server_fd) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &addr_len); if (client_fd == -1) { perror("accept"); continue; } set_nonblocking(client_fd); // 创建连接对象 connection_t *conn = malloc(sizeof(connection_t)); if (!conn) { close(client_fd); continue; } *conn = (connection_t){ .fd = client_fd, .read_len = 0, .write_len = 0, .write_sent = 0, .keep_alive = 0, .client_addr = client_addr }; // 添加到epoll监控 ev . events = EPOLLIN | EPOLLET; // 边缘触发模式 ev . data . ptr = conn; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) { perror("epoll_ctl: client_fd"); free(conn); close(client_fd); continue; } connections[client_fd] = conn; char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr . sin_addr, ip_str, INET_ADDRSTRLEN); char log_msg[128]; snprintf(log_msg, sizeof(log_msg), "Client connected: %s:%d", ip_str, ntohs(client_addr . sin_port)); log_message("INFO", log_msg, 1); } // 客户端事件 else { connection_t *conn = events[i] . data . ptr; // 可读事件 if (events[i] . events & EPOLLIN) { ssize_t count = read(conn -> fd, conn -> read_buf + conn -> read_len, BUFFER_SIZE - conn -> read_len - 1); if (count == -1) { // 错误或EAGAIN if (errno != EAGAIN && errno != EWOULDBLOCK) { goto close_conn; } } else if (count == 0) { // 客户端关闭连接 goto close_conn; } else { conn -> read_len += count; conn -> read_buf[conn -> read_len] = '\0'; // 检查是否收到完整请求 if (strstr(conn -> read_buf, "\r\n\r\n")) { if (debug_mode) { log_message("REQUEST", conn -> read_buf, 1); } handle_request(conn); // 修改为监听写事件 ev . events = EPOLLOUT | EPOLLET; ev . data . ptr = conn; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn -> fd, &ev); } } } // 可写事件 if (events[i] . events & EPOLLOUT) { if (conn -> write_len > 0) { ssize_t count = write(conn -> fd, conn -> write_buf + conn -> write_sent, conn -> write_len - conn -> write_sent); if (count == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { goto close_conn; } } else { conn -> write_sent += count; // 全部发送完成 if (conn -> write_sent >= conn -> write_len) { if (conn -> keep_alive) { // 保持连接,重置状态 conn -> read_len = 0; conn -> write_len = 0; conn -> write_sent = 0; // 修改为监听读事件 ev . events = EPOLLIN | EPOLLET; ev . data . ptr = conn; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn -> fd, &ev); } else { goto close_conn; } } } } } // 错误事件 if (events[i] . events & (EPOLLERR | EPOLLHUP)) { goto close_conn; } continue; close_conn: // 关闭连接并清理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn -> fd, NULL); close(conn -> fd); free(conn); connections[conn -> fd] = NULL; } } } close(server_fd); close(epoll_fd); return 0; } 前一个代码是可正常运行的,后一个不行,请你基于此信息再分析原因
08-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值