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

如何为基于边缘触发epoll()的C语言HTTP服务器实现长连接?

边缘触发epoll实现连接超时管理方案

问题场景

我基于边缘触发的epoll实现服务器,需要实现以下逻辑:

process_request(client);
client_timestamp = curr_time;

// 执行其他操作,可能再次调用process_request(client)并更新时间戳

when (curr_time - client_timestamp > TIMEOUT) close(client);

但存在两个限制:

  • 不能用sleep(),避免阻塞影响性能
  • 不能为每个连接单独开线程,防止连接过多时耗尽线程资源

核心解决方案:统一定时器集合+epoll_wait超时控制

不用额外线程或sleep,而是把所有连接的超时事件集中管理,结合epoll的超时等待机制,让epoll_wait要么在有IO事件时返回,要么在到了最早超时时间时返回,一次性处理所有超时和IO事件。

具体实现步骤

  • 为每个连接维护活跃时间戳:给每个客户端连接结构体添加last_active字段,每次调用process_request后更新该时间戳。
  • 用有序结构管理超时队列:把所有连接按「超时时间(last_active + TIMEOUT)」排序,比如用最小堆、红黑树或有序链表,快速找到最早要超时的连接。
  • 动态设置epoll_wait的超时参数:每次epoll_wait前,计算当前最早超时时间与当前时间的差值,作为epoll_wait的超时参数。这样epoll不会无意义阻塞,一旦到了最早超时时间就会返回。
  • 先处理超时,再处理IO事件:epoll_wait返回后,先遍历超时队列,关闭所有超时的连接并从epoll和队列中移除;再处理触发的IO事件,更新对应连接的时间戳并调整其在超时队列中的位置。

代码示例(简化版)

定义客户端结构体与超时链表

#include <sys/epoll.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>

#define TIMEOUT 300  // 超时时间,单位:秒(示例为5分钟)
#define MAX_EVENTS 1024

// 客户端连接结构体
typedef struct client {
    int fd;
    time_t last_active;  // 最后活跃时间戳
    // 其他连接相关数据:比如接收缓冲区、请求状态等
} client_t;

// 超时链表节点(按last_active+TIMEOUT升序排列)
typedef struct timeout_node {
    client_t *client;
    struct timeout_node *next;
} timeout_node_t;

timeout_node_t *timeout_head = NULL;
int listen_fd;  // 监听套接字,需提前初始化

辅助函数:插入/移除超时链表节点

// 将客户端插入超时链表的正确位置,保持链表按超时时间升序
void insert_to_timeout_list(client_t *c) {
    timeout_node_t *new_node = malloc(sizeof(timeout_node_t));
    new_node->client = c;
    new_node->next = NULL;

    if (timeout_head == NULL) {
        timeout_head = new_node;
        return;
    }

    time_t new_timeout = c->last_active + TIMEOUT;
    // 插入到头部
    if (new_timeout < timeout_head->client->last_active + TIMEOUT) {
        new_node->next = timeout_head;
        timeout_head = new_node;
        return;
    }

    // 遍历找到插入位置
    timeout_node_t *curr = timeout_head;
    while (curr->next != NULL && curr->next->client->last_active + TIMEOUT <= new_timeout) {
        curr = curr->next;
    }
    new_node->next = curr->next;
    curr->next = new_node;
}

// 从超时链表中移除指定客户端
void remove_from_timeout_list(client_t *c) {
    if (timeout_head == NULL) return;

    timeout_node_t *prev = NULL;
    timeout_node_t *curr = timeout_head;
    while (curr != NULL && curr->client != c) {
        prev = curr;
        curr = curr->next;
    }

    if (curr == NULL) return;

    if (prev == NULL) {
        timeout_head = curr->next;
    } else {
        prev->next = curr->next;
    }
    free(curr);
}

// 根据文件描述符查找客户端(需自己实现,比如用哈希表)
client_t *find_client_by_fd(int fd) {
    // 示例:实际可通过哈希表快速查找,这里简化返回
    timeout_node_t *curr = timeout_head;
    while (curr != NULL) {
        if (curr->client->fd == fd) {
            return curr->client;
        }
        curr = curr->next;
    }
    return NULL;
}

主事件循环

int main() {
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 注册监听套接字到epoll(边缘触发)
    struct epoll_event listen_ev;
    listen_ev.events = EPOLLIN | EPOLLET;
    listen_ev.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &listen_ev) == -1) {
        perror("epoll_ctl add listen_fd");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[MAX_EVENTS];
    while (1) {
        time_t now = time(NULL);
        int wait_ms = -1;

        // 计算epoll_wait的超时时间
        if (timeout_head != NULL) {
            time_t earliest_timeout = timeout_head->client->last_active + TIMEOUT;
            if (earliest_timeout > now) {
                wait_ms = (int)(earliest_timeout - now) * 1000;  // 转毫秒
            } else {
                wait_ms = 0;  // 已有超时连接,立即返回处理
            }
        }

        // 等待IO事件或超时
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, wait_ms);
        if (nfds == -1) {
            perror("epoll_wait");
            continue;
        }

        // 第一步:处理所有超时连接
        now = time(NULL);
        while (timeout_head != NULL) {
            client_t *c = timeout_head->client;
            if (now - c->last_active > TIMEOUT) {
                // 超时,关闭连接
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, NULL);
                close(c->fd);
                // 从链表移除并释放资源
                timeout_node_t *tmp = timeout_head;
                timeout_head = timeout_head->next;
                free(tmp->client);
                free(tmp);
            } else {
                // 后续连接都未超时,退出循环
                break;
            }
        }

        // 第二步:处理IO事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].events & EPOLLIN) {
                int fd = events[i].data.fd;
                if (fd == listen_fd) {
                    // 处理新连接
                    int client_fd = accept(listen_fd, NULL, NULL);
                    if (client_fd == -1) {
                        perror("accept");
                        continue;
                    }

                    client_t *new_client = malloc(sizeof(client_t));
                    new_client->fd = client_fd;
                    new_client->last_active = now;

                    // 注册客户端套接字到epoll(边缘触发)
                    struct epoll_event client_ev;
                    client_ev.events = EPOLLIN | EPOLLET;
                    client_ev.data.fd = client_fd;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_ev) == -1) {
                        perror("epoll_ctl add client_fd");
                        close(client_fd);
                        free(new_client);
                        continue;
                    }

                    // 插入超时链表
                    insert_to_timeout_list(new_client);
                } else {
                    // 处理客户端请求
                    client_t *c = find_client_by_fd(fd);
                    if (c == NULL) continue;

                    // 处理请求(边缘触发需一次性读完数据)
                    int ret = process_request(c);
                    if (ret == 0) {
                        // 请求处理成功,更新活跃时间戳
                        remove_from_timeout_list(c);
                        c->last_active = now;
                        insert_to_timeout_list(c);
                    } else {
                        // 处理失败或客户端断开,关闭连接
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, NULL);
                        close(c->fd);
                        remove_from_timeout_list(c);
                        free(c);
                    }
                }
            }
            // 可扩展处理EPOLLOUT等其他事件
        }
    }

    return 0;
}

优化建议

  • 替换有序链表为高效数据结构:示例中的链表插入/删除是O(n)复杂度,高并发场景下建议用最小堆(O(logn))或红黑树,甚至可以用Linux内核的rbtree实现。
  • 结合timerfd简化定时器管理:创建timerfd并加入epoll,每次计算最早超时时间后调用timerfd_settime更新定时器,当timerfd触发可读事件时,批量处理所有超时连接,无需自己维护超时队列。
  • 用哈希表快速查找客户端:示例中find_client_by_fd用链表遍历效率低,实际可通过哈希表(比如uthash库)实现O(1)复杂度的查找。

内容的提问来源于stack exchange,提问作者Sgg8

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:05:28