TKeed 长连接深度解析:非阻塞 I/O + epoll ET 模式实现 keep-alive 的完整指南 TKeed 长连接深度解析非阻塞 I/O epoll ET 模式实现 keep-alive 的完整指南【免费下载链接】TKeed High Performance HTTP WebServer项目地址: https://gitcode.com/gh_mirrors/tk/TKeedTKeed是一款高性能 HTTP WebServer其核心亮点之一就是基于非阻塞 I/O epoll 边缘触发ET模式实现的HTTP 长连接keep-alive。本文将带你完整走一遍 TKeed 长连接的实现原理从 Reactor 并发模型、非阻塞套接字的设置到 ET 模式下连接状态的切换再到空闲连接的定时器回收最后附上真实压测数据帮你彻底搞懂 keep-alive 服务端是怎么落地的。一、为什么需要 keep-alive 长连接 传统短连接模式下客户端每发起一次 HTTP 请求都要经历完整的TCP 三次握手 → 请求响应 → 四次挥手高频场景下建连和断开的开销非常可观。keep-alive长连接的思路很直接一次请求响应完成后不断开 TCP 连接直接进入等待下一次请求的状态后续请求复用同一条连接省去重复建连成本服务端通过超时机制回收长时间无请求的空闲连接避免资源被僵尸连接占满。HTTP/1.1 协议默认就是持久连接客户端请求头携带Connection: keep-alive时服务端需要正确识别并延续这条连接——这正是 TKeed 做的事。二、TKeed 的整体架构Reactor 模型 线程池TKeed 采用经典的Reactor 并发模型主线程负责事件监听与分发工作线程池负责处理具体的请求任务。启动流程非常清晰位于 src_code/main.c读取配置文件 src_code/tkeed.conf端口、线程数、静态资源根目录创建监听套接字并设置为非阻塞创建 epoll 实例注册监听描述符事件类型为EPOLLIN | EPOLLET注意边缘触发模式初始化线程池与定时器进入主循环epoll_wait→ 处理超时请求 → 分发事件。// main.c 主循环示意 while(1){ int time tk_find_timer(); // 计算最近的定时器等待时间 int events_num tk_epoll_wait(epoll_fd, events, MAXEVENTS, -1); tk_handle_expire_timers(); // 处理超时的空闲连接 tk_handle_events(epoll_fd, listen_fd, events, events_num, conf.root, tp); }三、非阻塞 I/O长连接的基础设施⚙️ 非阻塞套接字是 ET 模式的前置条件核心代码在 src_code/util.c 的make_socket_non_blocking监听描述符main中初始化后立即设置为非阻塞每个新连接accept_connection接受连接后马上调用make_socket_non_blocking(accept_fd)。关键在于请求处理函数 src_code/http.c 中的do_request它用一段读循环 EAGAIN 判断实现了读完即止、读空即等的行为n_read read(fd, plast, remain_size); if(n_read 0) goto err; // 客户端主动断开 if(n_read 0 errno ! TK_AGAIN) goto err; // 真实错误 if(n_read 0 errno TK_AGAIN) break; // ⭐ 暂无数据退出读循环⭐ 这个EAGAIN分支就是长连接的心跳读循环中一旦发现没有新数据可读说明当前请求已经处理完连接回到空闲状态——此时不关闭描述符而是把连接重新挂回 epoll 等待下一次请求详见下一节。四、epoll ET 模式为什么长连接偏爱边缘触发TKeed 中所有连接描述符的注册/修改都集中在 src_code/epoll.c关键事件标志组合是// accept_connection新连接注册util.c tk_epoll_add(epoll_fd, accept_fd, request, (EPOLLIN | EPOLLET | EPOLLONESHOT));三个标志各司其职标志作用EPOLLIN监听可读事件EPOLLET边缘触发只有状态变化时通知一次EPOLLONESHOT触发一次后自动注销保证同一连接任一时刻只被一个线程处理避免多线程并发读写为什么 ET 模式对长连接友好LT水平触发下只要缓冲区有数据就反复通知空闲连接不会触发——但一旦进入等待下一个请求状态LT 下容易因重复注册/漏读产生状态混乱ET 模式下收到新请求数据是一个明确的状态跳变天然契合空闲 → 有请求 → 处理完 → 再空闲的长连接生命周期。请求处理完成后的再挂回操作同样在do_request末尾tk_epoll_mod(request-epoll_fd, request-fd, request, (EPOLLIN | EPOLLET | EPOLLONESHOT)); // 修改事件类型重新监听 tk_add_timer(request, TIMEOUT_DEFAULT, tk_http_close_conn); // 挂上空闲超时五、keep-alive 判定与响应头协商这条连接到底要不要保持由请求头决定处理逻辑在 src_code/http_request.c头部解析采用状态机见 src_code/http_parse.c而非简单的字符串匹配tk_http_process_connection识别Connection头若为keep-alive则置out-keep_alive 1默认值就是 1即默认保持连接响应时src_code/http.c 的serve_static会按 keep-alive 状态写出响应头if(out-keep_alive){ sprintf(header, %sConnection: keep-alive\r\n, header); sprintf(header, %sKeep-Alive: timeout%d\r\n, header, TIMEOUT_DEFAULT); }同时响应头还携带Content-length与Last-Modified支持If-Modified-Since条件请求命中则返回 304这些都是持久连接下正确分帧、协商缓存的前提。 完整生命周期可概括为新连接ETONESHOT 注册→ 超时定时器挂起 → 线程池解析并响应请求 → keep-alive 则 epoll_mod 重新监听 重置定时器 → 超时/出错则 close 释放六、空闲连接回收小根堆定时器keep-alive 的副作用是空闲连接会一直挂着TKeed 用基于小根堆的定时器src_code/timer.c src_code/priority_queue.c解决TIMEOUT_DEFAULT 500ms连接空闲超过该阈值即判定超时超时处理函数直接指定为tk_http_close_conn——关闭描述符、释放请求结构体采用惰性删除tk_del_timer只置删除标记真正的节点回收在tk_find_timer/tk_handle_expire_timers扫描队首时顺带完成省去堆内定位开销主循环中tk_find_timer返回距最近一个定时器的毫秒差可精确控制epoll_wait的超时等待避免无谓的忙轮询。七、性能验证webbench 压测实测项目用 WebBench 做了 1000 并发、60s 的压测工具源码见 test_unit/webbench/测试报告见 测试及改进.md4 worker 下 1 分钟处理671102个页面全部成功1000 并发满负荷下8 个 worker 各线程 CPU 使用率仅约5%总负载 1.03 左右说明ET 模式 非阻塞读 定时器精确等待的组合有效避免了空转浪费。八、总结长连接实现的 5 个要点✅非阻塞套接字是 ET 模式的前提EAGAIN是请求结束、进入空闲的信号✅EPOLLIN | EPOLLET | EPOLLONESHOT保证事件不遗漏且同一连接单线程独占✅ 响应完成后用epoll_ctl MOD把连接重新挂回事件循环而非关闭✅小根堆定时器 惰性删除以 O(log n) 复杂度精准回收空闲连接✅ 主循环用tk_find_timer动态计算epoll_wait超时低负载零空转。想动手验证的话克隆仓库后按 src_code/makefile 构建配置好 src_code/tkeed.conf 中的port、thread_num与root即可启动体验。相关设计文档可参考 并发模型.md、核心结构体.md 与 架构分析.md 【免费下载链接】TKeed High Performance HTTP WebServer项目地址: https://gitcode.com/gh_mirrors/tk/TKeed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考