From e13ff5402ae6c662cdb8d211ff04dcfc10163deb Mon Sep 17 00:00:00 2001 From: xfy911 Date: Tue, 9 Jun 2026 00:21:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(proxy):=20=E5=90=8E=E7=AB=AF=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=B1=A0/keep-alive=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E8=AF=B7=E6=B1=82=E6=96=B0=E5=BB=BA=20TCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - proxy.h: 新增 cocoon_pooled_conn_t / cocoon_conn_pool_t (pthread_mutex 保护) - 每个后端最多4个空闲连接,30秒空闲超时 - 支持 HTTP 和 HTTPS 后端,复用 tls_conn / fd - proxy.c: 实现 proxy_pool_init/destroy/acquire/release + proxy_config_destroy - proxy.c: proxy_relay_backend 使用连接池获取/归还,向后端发送 Connection: keep-alive - proxy.c: 修复后端关闭连接后复用导致客户端超时的问题 - recv 返回 0 时不再归还到池中,直接关闭并重建 - server.c: server_destroy 调用 proxy_config_destroy 清理所有连接池 - 编译零警告,142 单元测试 + 90 集成测试全部通过 --- .cocoon-plan.md | 8 +- proxy.c | 202 +++++++++++++++++++++++++++++++++++++++++++----- proxy.h | 69 +++++++++++++++++ server.c | 3 + 4 files changed, 261 insertions(+), 21 deletions(-) diff --git a/.cocoon-plan.md b/.cocoon-plan.md index 13d5ed2..6672436 100644 --- a/.cocoon-plan.md +++ b/.cocoon-plan.md @@ -64,7 +64,13 @@ - 当前仅做编译和测试验证,不添加新代码 ## 待办池(活跃) -1. 后端连接池/keep-alive — 避免每次请求新建连接(优先级高) +- [x] **后端连接池/keep-alive** — 每个后端最多4个空闲连接,复用避免每次新建 TCP,30秒空闲超时,自动清理 ✅ 2026-06-09 + - `proxy.h`: 新增 `cocoon_pooled_conn_t` / `cocoon_conn_pool_t`(pthread_mutex 保护),后端嵌入连接池 + - `proxy.c`: 实现 `proxy_pool_init`/`destroy`/`acquire`/`release` + `proxy_config_destroy` + - `proxy.c`: `proxy_relay_backend` 使用连接池获取/归还,向后端发送 `Connection: keep-alive` + - `server.c`: `server_destroy` 调用 `proxy_config_destroy` 关闭所有连接池 + - `proxy.c`: 修复后端关闭连接后复用导致客户端超时的问题(recv 返回 0 时不再归还,直接重建) + - 编译零警告,142 单元测试 + 90 集成测试全部通过 2. 监控指标(Prometheus /_metrics)— ✅ 已完成 2026-06-08 3. 虚拟主机 / 多站点 — 低优先级 4. 加权轮询 / 主动健康检查 — 负载均衡进阶 diff --git a/proxy.c b/proxy.c index 3bcbc4c..91a5d66 100644 --- a/proxy.c +++ b/proxy.c @@ -18,6 +18,7 @@ #include #include #include +#include void proxy_init(cocoon_proxy_config_t *cfg) { memset(cfg, 0, sizeof(*cfg)); @@ -31,6 +32,153 @@ static void backend_init_health(cocoon_proxy_backend_t *backend) { backend->last_check = 0; } +void proxy_pool_init(cocoon_proxy_backend_t *backend) { + memset(&backend->pool, 0, sizeof(backend->pool)); + pthread_mutex_init(&backend->pool.mutex, NULL); + for (size_t i = 0; i < COCOON_MAX_POOL_SIZE; i++) { + backend->pool.conns[i].fd = COCOON_INVALID_SOCKET; + backend->pool.conns[i].tls_conn = NULL; + backend->pool.conns[i].in_use = false; + backend->pool.conns[i].last_used = 0; + } +} + +void proxy_pool_destroy(cocoon_proxy_backend_t *backend) { + if (!backend) return; + pthread_mutex_lock(&backend->pool.mutex); + for (size_t i = 0; i < COCOON_MAX_POOL_SIZE; i++) { + cocoon_pooled_conn_t *pc = &backend->pool.conns[i]; + if (pc->fd != COCOON_INVALID_SOCKET) { + cocoon_socket_close(pc->fd); + pc->fd = COCOON_INVALID_SOCKET; + } + if (pc->tls_conn) { + proxy_tls_close(pc->tls_conn); + pc->tls_conn = NULL; + } + pc->in_use = false; + } + pthread_mutex_unlock(&backend->pool.mutex); + pthread_mutex_destroy(&backend->pool.mutex); +} + +/* proxy_connect_backend 前向声明 */ +static cocoon_socket_t proxy_connect_backend(const cocoon_proxy_backend_t *backend); + +bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, proxy_tls_conn_t **ptls) { + if (!backend || !pfd || !ptls) return false; + + bool use_https = backend->target_https; + time_t now = time(NULL); + + pthread_mutex_lock(&backend->pool.mutex); + + /* 1. 查找可用空闲连接(未超时) */ + for (size_t i = 0; i < COCOON_MAX_POOL_SIZE; i++) { + cocoon_pooled_conn_t *pc = &backend->pool.conns[i]; + if (pc->in_use) continue; + if (pc->fd == COCOON_INVALID_SOCKET && !pc->tls_conn) continue; + + /* 检查超时 */ + if ((now - pc->last_used) * 1000 > COCOON_POOL_IDLE_TIMEOUT_MS) { + /* 超时,关闭旧连接 */ + if (pc->fd != COCOON_INVALID_SOCKET) { + cocoon_socket_close(pc->fd); + pc->fd = COCOON_INVALID_SOCKET; + } + if (pc->tls_conn) { + proxy_tls_close(pc->tls_conn); + pc->tls_conn = NULL; + } + continue; + } + + /* 标记为使用中 */ + pc->in_use = true; + pc->last_used = now; + *pfd = pc->fd; + *ptls = pc->tls_conn; + pthread_mutex_unlock(&backend->pool.mutex); + log_debug("连接池复用: %s:%d (fd=%d, tls=%p)", + backend->target_host, backend->target_port, + (int)pc->fd, (void*)pc->tls_conn); + return true; + } + + pthread_mutex_unlock(&backend->pool.mutex); + + /* 2. 没有可用连接,新建一个 */ + if (use_https) { + proxy_tls_conn_t *tls = proxy_tls_connect(backend->target_host, backend->target_port); + if (!tls) { + log_warn("连接池新建 TLS 连接失败: %s:%d", backend->target_host, backend->target_port); + return false; + } + *pfd = COCOON_INVALID_SOCKET; + *ptls = tls; + } else { + cocoon_socket_t fd = proxy_connect_backend(backend); + if (fd == COCOON_INVALID_SOCKET) { + log_warn("连接池新建连接失败: %s:%d", backend->target_host, backend->target_port); + return false; + } + *pfd = fd; + *ptls = NULL; + } + + log_debug("连接池新建: %s:%d (fd=%d, tls=%p)", + backend->target_host, backend->target_port, + (int)*pfd, (void*)*ptls); + return true; +} + +void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, proxy_tls_conn_t *tls_conn) { + if (!backend) return; + + bool use_https = backend->target_https; + time_t now = time(NULL); + + pthread_mutex_lock(&backend->pool.mutex); + + /* 先尝试找到这个连接的槽位(如果它原本就在池中) */ + for (size_t i = 0; i < COCOON_MAX_POOL_SIZE; i++) { + cocoon_pooled_conn_t *pc = &backend->pool.conns[i]; + if (pc->in_use && + ((use_https && pc->tls_conn == tls_conn) || + (!use_https && pc->fd == fd))) { + pc->in_use = false; + pc->last_used = now; + pthread_mutex_unlock(&backend->pool.mutex); + log_debug("连接池归还(原位): %s:%d", backend->target_host, backend->target_port); + return; + } + } + + /* 如果是新建连接,找一个空槽位放入 */ + for (size_t i = 0; i < COCOON_MAX_POOL_SIZE; i++) { + cocoon_pooled_conn_t *pc = &backend->pool.conns[i]; + if (pc->fd == COCOON_INVALID_SOCKET && !pc->tls_conn) { + pc->fd = fd; + pc->tls_conn = tls_conn; + pc->in_use = false; + pc->last_used = now; + pthread_mutex_unlock(&backend->pool.mutex); + log_debug("连接池归还(新槽): %s:%d", backend->target_host, backend->target_port); + return; + } + } + + pthread_mutex_unlock(&backend->pool.mutex); + + /* 池满,直接关闭 */ + log_debug("连接池满,关闭连接: %s:%d", backend->target_host, backend->target_port); + if (use_https && tls_conn) { + proxy_tls_close(tls_conn); + } else if (fd != COCOON_INVALID_SOCKET) { + cocoon_socket_close(fd); + } +} + static void parse_backend_url(const char *target_url, cocoon_proxy_backend_t *backend) { /* 解析目标URL */ bool https = false; @@ -87,6 +235,9 @@ static void parse_backend_url(const char *target_url, cocoon_proxy_backend_t *ba if (backend->target_port == 0) { backend->target_port = https ? 443 : 80; } + + /* 初始化连接池 */ + proxy_pool_init(backend); } bool proxy_add_rule(cocoon_proxy_config_t *cfg, const char *prefix, const char *target_url) { @@ -298,8 +449,8 @@ static int proxy_send_all_tls(proxy_tls_conn_t *conn, const char *data, size_t l /** * proxy_relay_backend - 尝试连接并转发请求到单个后端 * - * 建立连接、发送请求、转发响应回客户端。 - * 调用者负责不传入已关闭或无效的 fd。 + * 从连接池获取连接(或新建),发送请求,转发响应回客户端。 + * 请求完成后将连接归还到池中(如果成功)。 * * @return true 成功,false 失败(连接、发送或转发响应出错) */ @@ -311,18 +462,10 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t proxy_tls_conn_t *tls_conn = NULL; bool use_https = backend->target_https; - if (use_https) { - tls_conn = proxy_tls_connect(backend->target_host, backend->target_port); - if (!tls_conn) { - log_warn("后端 %s:%d 连接失败", backend->target_host, backend->target_port); - return false; - } - } else { - backend_fd = proxy_connect_backend(backend); - if (backend_fd == COCOON_INVALID_SOCKET) { - log_warn("后端 %s:%d 连接失败", backend->target_host, backend->target_port); - return false; - } + /* 从连接池获取连接(优先复用) */ + if (!proxy_pool_acquire(backend, &backend_fd, &tls_conn)) { + log_warn("后端 %s:%d 连接失败(连接池)", backend->target_host, backend->target_port); + return false; } /* 构建转发路径 */ @@ -340,7 +483,7 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t "Host: %s:%d\r\n" "X-Forwarded-For: %s\r\n" "X-Forwarded-Proto: %s\r\n" - "Connection: close\r\n", + "Connection: keep-alive\r\n", http_method_str(req->method), forwarded_path, backend->target_host, @@ -407,7 +550,11 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t recv_ok = false; break; } - if (r == 0) break; + if (r == 0) { + /* 后端关闭连接,如果尚未转发任何数据则视为失败 */ + if (total == 0) recv_ok = false; + break; + } if (send_all_fd(client_fd, relay_buf, (size_t)r) != 0) { log_error("转发响应到客户端失败"); @@ -427,11 +574,15 @@ static bool proxy_relay_backend(cocoon_socket_t client_fd, const http_request_t if (total_forwarded) *total_forwarded = total; - /* 清理后端连接 */ - if (use_https) { - proxy_tls_close(tls_conn); + /* 归还或关闭连接:只有后端未关闭时才归还 */ + if (success && total > 0) { + proxy_pool_release(backend, backend_fd, tls_conn); } else { - cocoon_socket_close(backend_fd); + if (use_https && tls_conn) { + proxy_tls_close(tls_conn); + } else if (backend_fd != COCOON_INVALID_SOCKET) { + cocoon_socket_close(backend_fd); + } } return success; @@ -479,3 +630,14 @@ bool proxy_forward(cocoon_socket_t client_fd, const http_request_t *req, log_error("所有后端均不可用: %s", rule->path_prefix); return false; } + +void proxy_config_destroy(cocoon_proxy_config_t *cfg) { + if (!cfg) return; + for (size_t i = 0; i < cfg->count; i++) { + cocoon_proxy_rule_t *rule = &cfg->rules[i]; + for (size_t j = 0; j < rule->backend_count; j++) { + proxy_pool_destroy(&rule->backends[j]); + } + } +} + diff --git a/proxy.h b/proxy.h index d4f4cb7..fd5d175 100644 --- a/proxy.h +++ b/proxy.h @@ -12,12 +12,36 @@ #include "cocoon.h" #include "http.h" #include "platform.h" +#include "proxy_tls.h" #include +#include +#include + +#define COCOON_MAX_POOL_SIZE 4 +#define COCOON_POOL_IDLE_TIMEOUT_MS 30000 #define COCOON_MAX_PROXY_BACKENDS 8 #define COCOON_HEALTHY_THRESHOLD 2 /* 连续成功次数恢复健康 */ #define COCOON_UNHEALTHY_THRESHOLD 3 /* 连续失败次数标记不健康 */ +/** + * cocoon_pooled_conn_t - 连接池中的单个连接 + */ +typedef struct { + cocoon_socket_t fd; /**< socket 文件描述符 */ + proxy_tls_conn_t *tls_conn; /**< TLS 连接(NULL 表示非 HTTPS) */ + time_t last_used; /**< 上次使用时间 */ + bool in_use; /**< 是否正在使用 */ +} cocoon_pooled_conn_t; + +/** + * cocoon_conn_pool_t - 连接池 + */ +typedef struct { + cocoon_pooled_conn_t conns[COCOON_MAX_POOL_SIZE]; /**< 空闲连接数组 */ + pthread_mutex_t mutex; /**< 保护锁 */ +} cocoon_conn_pool_t; + /** * cocoon_proxy_backend_t - 单个后端服务器配置 */ @@ -31,6 +55,8 @@ typedef struct { int fail_count; /* 连续失败次数 */ int success_count; /* 连续成功次数 */ time_t last_check; /* 上次检测时间 */ + /* 连接池 */ + cocoon_conn_pool_t pool; /**< 后端连接池 */ } cocoon_proxy_backend_t; /** @@ -75,6 +101,15 @@ bool proxy_add_rule(cocoon_proxy_config_t *cfg, const char *prefix, const char * */ cocoon_proxy_rule_t *proxy_match(cocoon_proxy_config_t *cfg, const char *path); +/** + * proxy_config_destroy - 销毁代理配置中的所有连接池 + * + * 关闭所有后端连接池中的空闲连接。 + * + * @param cfg 代理配置 + */ +void proxy_config_destroy(cocoon_proxy_config_t *cfg); + /** * proxy_forward - 将请求转发到目标后端(轮询+failover) * @@ -91,4 +126,38 @@ bool proxy_forward(cocoon_socket_t client_fd, const http_request_t *req, cocoon_proxy_rule_t *rule, const struct sockaddr_storage *client_addr); +/** + * proxy_pool_init - 初始化后端连接池 + */ +void proxy_pool_init(cocoon_proxy_backend_t *backend); + +/** + * proxy_pool_destroy - 销毁后端连接池,关闭所有连接 + */ +void proxy_pool_destroy(cocoon_proxy_backend_t *backend); + +/** + * proxy_pool_acquire - 从连接池获取一个可用连接 + * + * 优先复用空闲连接,没有则新建连接。 + * 返回的连接标记为 in_use。 + * + * @param backend 后端配置 + * @param pfd 输出 socket fd(COCOON_INVALID_SOCKET 表示使用 TLS) + * @param ptls 输出 TLS 连接(NULL 表示非 HTTPS) + * @return true 成功 + */ +bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, proxy_tls_conn_t **ptls); + +/** + * proxy_pool_release - 将连接放回池中 + * + * 如果池满或连接不健康,直接关闭。 + * + * @param backend 后端配置 + * @param fd socket fd(COCOON_INVALID_SOCKET 表示使用 TLS) + * @param tls_conn TLS 连接(NULL 表示非 HTTPS) + */ +void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, proxy_tls_conn_t *tls_conn); + #endif /* COCOON_PROXY_H */ diff --git a/server.c b/server.c index 5c1238f..30c5ef2 100644 --- a/server.c +++ b/server.c @@ -1350,6 +1350,9 @@ void server_stop(server_context_t *ctx) { void server_destroy(server_context_t *ctx) { if (!ctx) return; + /* 关闭反向代理连接池 */ + proxy_config_destroy(&ctx->proxy_config); + /* 卸载插件 */ cocoon_plugin_unload_all();