You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用poll()优雅关闭基于poll循环的数据库服务器

如何使用poll()优雅关闭基于poll循环的数据库服务器

看起来你已经搭好了一个基于poll()的多客户端数据库服务器框架,但目前的实现收到终止信号(比如Ctrl+C触发的SIGINT)时会直接退出,很可能导致数据库未保存、客户端连接未正常关闭这类问题。下面我一步步教你改造代码,实现优雅关闭——也就是安全收尾所有资源后再退出。

一、先设置全局退出标志

首先我们需要一个全局变量来标记服务器是否需要退出,绝对不能在信号处理函数里直接调用exit(),因为信号处理函数里不能调用像printf()、close()这类非异步信号安全的函数。

在代码顶部(比如srvpoll.h或者main所在的文件)添加全局变量:

#include <stdbool.h>
#include <errno.h>
// 全局退出标志,volatile确保编译器不会优化它
volatile sig_atomic_t g_quit = 0;

然后实现信号处理函数:

void handle_signal(int sig) {
    if (sig == SIGINT) {
        g_quit = 1; // 只设置退出标志,不做其他复杂操作
    }
}

二、改造poll循环,支持优雅退出

现在修改你的poll_loop函数,让它能检测到g_quit标志,并且在退出前完成所有收尾工作:

void poll_loop(int dbfd, unsigned short port, struct dbheader_t **dbhdr, struct employee_t **employees) {
    init_clients(CLIENTS);
    int nfds=1;
    struct pollfd fds[257];
    memset(&fds,'\0',sizeof(fds));
    struct sockaddr_in mysocket;
    memset(&mysocket,0,sizeof(struct sockaddr_in));
    mysocket.sin_family=AF_INET;
    mysocket.sin_port=htons(port);
    mysocket.sin_addr.s_addr=inet_addr("127.0.0.1");
    int sockfd,new_fd;
    if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    int yes=1;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes);
    if(bind(sockfd,(struct sockaddr *)&mysocket,sizeof(struct sockaddr_in))==-1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    if(listen(sockfd,BACKLOG)==-1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    fds[0].fd=sockfd;
    fds[0].events=POLLIN;

    while(1) {
        // 检查退出标志,触发则跳出循环执行收尾
        if (g_quit) {
            printf("\n收到退出信号,开始优雅关闭...\n");
            break;
        }

        // 重新构建客户端fd列表(原逻辑保留)
        int ii=1;
        for(int i=0;i<256;++i) {
            if(CLIENTS[i].fd!=-1) {
                fds[ii].fd=CLIENTS[i].fd;
                fds[ii].events=POLLIN;
                ++ii;
            }
        }

        // 改成1秒超时,确保能及时检测退出标志
        int n_events= poll(fds,nfds,1000);
        if(n_events==-1) {
            // 被信号中断时,继续循环而非退出
            if (errno == EINTR) continue;
            perror("poll");
            exit(EXIT_FAILURE);
        }
        if(n_events==0) {
            // 超时无事件,继续循环
            continue;
        }

        // 仅在未触发退出时,才接受新连接
        if (!g_quit && (fds[0].revents& POLLIN)) {
            if((new_fd=accept(sockfd,NULL,NULL))==-1) {
                perror("accept");
                continue;
            }
            int slot=freeSlot(CLIENTS);
            if(slot==-1) {
                printf("server full\n");
                close(new_fd);
            } else {
                CLIENTS[slot].fd=new_fd;
                CLIENTS[slot].state=STATE_CONNECTED;
                nfds++;
                printf("Slot %d has fd %d\n",slot,CLIENTS[slot].fd);
            }
            n_events--;
        }

        // 处理客户端请求(原逻辑保留)
        for(int i=1;i<=nfds&&n_events>0;++i) {
            if(fds[i].revents&POLLIN) {
                n_events--;
                int fd=fds[i].fd;
                int client_slot=find_fd(CLIENTS,fd);
                if(client_slot==-1) {
                    printf("client does not exist\n");
                    continue;
                }
                ssize_t bytes_read=read(fd,CLIENTS[client_slot].buffer,sizeof(CLIENTS[client_slot].buffer));
                if(bytes_read<=0) {
                    printf("Client %d has disconnected or error\n",client_slot);
                    close(fd);
                    CLIENTS[client_slot].fd=-1;
                    memset(CLIENTS[client_slot].buffer,'\0',sizeof(CLIENTS[client_slot].buffer));
                    nfds--;
                } else {
                    if(CLIENTS[client_slot].state==STATE_CONNECTED||CLIENTS[client_slot].state!=STATE_MSG) {
                        CLIENTS[client_slot].state=STATE_HELLO;
                    }
                    handle_client_fsm(dbfd,dbhdr,employees,&CLIENTS[client_slot]);
                }
            }
        }
    }

    // 优雅关闭的核心收尾工作
    printf("开始清理资源...\n");
    // 1. 关闭监听socket,停止接受新连接
    close(sockfd);
    // 2. 关闭所有活跃客户端连接
    for (int i = 0; i < 256; ++i) {
        if (CLIENTS[i].fd != -1) {
            printf("关闭客户端连接 %d\n", CLIENTS[i].fd);
            close(CLIENTS[i].fd);
            CLIENTS[i].fd = -1;
        }
    }
    // 3. 强制保存数据库到文件(调用你已有的持久化函数)
    printf("保存数据库状态...\n");
    if (write_employees(dbfd, *dbhdr, *employees) != STATUS_SUCCESS) {
        fprintf(stderr, "警告:数据库保存失败,部分数据可能丢失\n");
    }
}

三、完善main函数的信号注册和最终收尾

修改main函数,确保信号处理正常工作,并且在poll_loop返回后完成最后清理:

// 声明全局退出标志(如果放在头文件中可省略)
extern volatile sig_atomic_t g_quit;

int main(int argc, char *argv[]) {
    // 注册SIGINT信号处理
    if(signal(SIGINT, handle_signal)==SIG_ERR) {
        perror("signal");
        return 1;
    }

    // 原命令行解析逻辑保留
    char *filepath =NULL;
    bool newfile=false;
    int dbfd=-1;
    struct dbheader_t *dbhd=NULL;
    struct employee_t *employees = NULL;
    unsigned short port=8080; // 可从命令行参数解析
    int c;

    while((c= getopt(argc,argv,"nf:a:lr:u:p:"))!=-1) {
        switch(c) {
            case 'n': newfile=true; break;
            case 'f': filepath = optarg; break;
            case 'p': port=atoi(optarg); break;
            // 其他参数处理保留...
            case '?': print_usage(argv); return 1;
            default: return -1;
        }
    }

    // 原数据库初始化逻辑保留(打开/创建、读取数据等)
    if(filepath==NULL) {
        printf("Filepath is a required argument\n");
        print_usage(argv);
        return 0;
    }
    // ...省略数据库打开、读取等代码...

    // 启动poll循环,循环退出后执行收尾
    poll_loop(dbfd, port, &dbhd, &employees);

    // 最终资源清理
    printf("完成最终清理...\n");
    if (dbfd != -1) close(dbfd);
    free(dbhd);
    free(employees);

    printf("服务器已优雅退出\n");
    return 0;
}

关键注意事项

  1. 信号处理函数的限制:只能在信号处理函数里设置标志,不能调用printf()、close()这类非异步安全函数,避免触发未定义行为。
  2. poll的EINTR错误:当poll()被信号中断时会返回-1且errno=EINTR,这时候要继续循环,不要直接退出。
  3. 停止接受新连接:触发退出标志后,不再处理新的accept()请求,避免收尾过程中新增客户端。
  4. 数据库强制保存:一定要在退出前调用持久化函数,确保所有内存中的修改都写入文件。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:24:32