Fork 服务器设计
一、fork 函数简介
fork()函数是UNIX或类UNIX系统中用于创建新进程的系统调用,它通过复制当前进程来创建一个子进程,子进程几乎与父进程完全相同,包括代码段、数据段、BSS段、堆和栈等所有用户空间信息,在内核中,操作系统会重新为子进程申请一个任务队列项(task_struct结构),并将其放入双向循环链表中进行调度和管理。
当fork()函数被调用时,它会返回两次:一次是在父进程中返回子进程的PID;另一次是在子进程中返回0,通过检查fork()的返回值,可以判断当前是运行在父进程中还是子进程中。
二、多进程服务器实现
多进程服务器通常使用fork()函数来处理并发客户端连接,以下是一个基本的多进程TCP服务器的设计步骤和代码示例:
1、创建监听套接字:使用socket()函数创建一个套接字,并绑定到服务器的IP地址和端口号上,然后使用listen()函数开始监听客户端连接请求。
2、等待客户端连接:使用accept()函数阻塞等待客户端的连接请求,accept()函数会返回一个新的套接字描述符(conn_fd),用于与客户端通信。
3、创建子进程:每当有新的客户端连接时,父进程会调用fork()函数创建一个子进程来处理该连接,父进程关闭连接套接字(conn_fd),继续等待新的客户端连接;子进程关闭监听套接字(listen_fd),专注于处理与客户端的数据交互。
4、处理客户端请求:子进程使用read()和write()函数与客户端进行数据传输,传输完成后,关闭连接套接字并退出子进程。
5、清理工作:父进程在接收到信号(如SIGCHLD)时,需要调用wait()或waitpid()函数来回收已终止子进程的资源,避免产生僵尸进程。
以下是一个简单的多进程TCP服务器的代码示例(C语言):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> void handle_client(int client_sock) { char buffer[1024]; int n; while ((n = read(client_sock, buffer, sizeof(buffer))) > 0) { write(client_sock, buffer, n); // Echo back to client } close(client_sock); exit(0); // Child process exits } int main() { int listen_fd, conn_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); pid_t pid; // Create listening socket listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { perror("socket"); exit(EXIT_FAILURE); } // Bind to port memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080); if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); exit(EXIT_FAILURE); } // Listen for connections if (listen(listen_fd, 10) < 0) { perror("listen"); exit(EXIT_FAILURE); } // Set up signal handler to clean up zombie processes signal(SIGCHLD, SIG_IGN); printf("Server is running on port 8080... "); while (1) { // Accept a new connection conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len); if (conn_fd < 0) { perror("accept"); continue; } // Fork a new process to handle the connection pid = fork(); if (pid < 0) { perror("fork"); close(conn_fd); continue; } else if (pid == 0) { // This is the child process close(listen_fd); // Child doesn't need the listener handle_client(conn_fd); } else { // This is the parent process close(conn_fd); // Parent doesn't need the connection descriptor } } close(listen_fd); return 0; }
三、相关问题与解答栏目
问题1:为什么在fork()之后需要关闭文件描述符?
答:在fork()之后,父子进程共享相同的文件描述符表,为了确保资源的正确释放和避免混乱,父子进程需要根据各自的角色关闭不需要的文件描述符,父进程关闭连接套接字(conn_fd),因为它只需要监听套接字(listen_fd);而子进程关闭监听套接字(listen_fd),因为它只需要处理与客户端的通信。
问题2:如何避免僵尸进程的产生?
答:僵尸进程是指已经终止但其父进程尚未回收其资源的进程,为了避免僵尸进程的产生,父进程可以使用wait()或waitpid()函数来回收已终止子进程的资源,还可以使用信号处理机制(如设置SIGCHLD信号的处理函数为SIG_IGN)来自动忽略子进程的终止状态,从而避免产生僵尸进程。
小伙伴们,上文介绍了“fork 服务器设计”的内容,你了解清楚吗?希望对你有所帮助,任何问题可以给我留言,让我们下期再见吧。
原创文章,作者:K-seo,如若转载,请注明出处:https://www.kdun.cn/ask/737969.html