MST

星途 面试题库

面试题:C语言prefork模型中的资源管理

在使用C语言实现Linux下的prefork模型高性能服务器时,如何有效地管理文件描述符、内存等资源以避免资源泄漏?请结合代码示例说明。
37.6万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试
  1. 文件描述符管理
    • 在父进程创建子进程前,打开的文件描述符默认会被子进程继承。需要合理处理这些文件描述符,避免不必要的资源占用和潜在的泄漏。
    • 对于监听套接字,父进程创建并绑定、监听后,子进程可以直接使用。但要注意在父进程中,监听套接字在子进程创建完毕后,若不再需要,应及时关闭。
    • 对于其他文件描述符,如日志文件等,根据实际需求决定是否需要子进程继承。若不需要,在fork之前可以使用fcntl设置FD_CLOEXEC标志,使子进程不会继承该文件描述符。
    • 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>

#define PORT 8080
#define BACKLOG 10

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr, cliaddr;

    // 创建监听套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    // 设置套接字选项,允许地址重用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // 填充服务器地址结构
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(listenfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(listenfd, BACKLOG) < 0) {
        perror("listen failed");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    // 设置监听套接字的FD_CLOEXEC标志,避免子进程意外继承
    int flags = fcntl(listenfd, F_GETFD, 0);
    fcntl(listenfd, F_SETFD, flags | FD_CLOEXEC);

    pid_t pid;
    // prefork创建多个子进程
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // 子进程处理逻辑
            close(listenfd); // 子进程不需要监听套接字,关闭以避免资源浪费
            while (1) {
                socklen_t len = sizeof(cliaddr);
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
                if (connfd < 0) {
                    perror("accept failed");
                    continue;
                }
                // 处理连接...
                close(connfd);
            }
            exit(EXIT_SUCCESS);
        }
    }
    // 父进程可以选择关闭监听套接字,这里假设父进程不再使用
    close(listenfd);

    // 父进程等待子进程结束
    while (wait(NULL) > 0);

    return 0;
}
  1. 内存管理
    • 在服务器程序中,动态分配的内存必须及时释放,以避免内存泄漏。例如,在处理客户端请求时,可能会分配内存用于存储请求数据、响应数据等。
    • 对于在函数内部动态分配的内存,函数结束前应确保释放。如果是在循环中分配内存,要注意在每次迭代中正确释放之前分配的内存。
    • 代码示例
#include <stdio.h>
#include <stdlib.h>

void handle_request() {
    char *buffer = (char *)malloc(1024);
    if (buffer == NULL) {
        perror("malloc failed");
        return;
    }
    // 处理请求,使用buffer...
    free(buffer);
}

int main() {
    // 模拟多次请求
    for (int i = 0; i < 10; i++) {
        handle_request();
    }
    return 0;
}
  1. 总结
    • 通过合理设置文件描述符的FD_CLOEXEC标志,以及在合适的位置关闭文件描述符,可以有效管理文件描述符资源。
    • 对于内存,遵循动态内存分配与释放的规则,在不再使用内存时及时调用free函数,从而避免资源泄漏,确保高性能服务器的稳定运行。