feat(proxy): 后端连接池/keep-alive,避免每次请求新建 TCP
- 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 集成测试全部通过
This commit is contained in:
parent
c3716cd704
commit
e13ff5402a
@ -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. 加权轮询 / 主动健康检查 — 负载均衡进阶
|
||||
|
||||
202
proxy.c
202
proxy.c
@ -18,6 +18,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
69
proxy.h
69
proxy.h
@ -12,12 +12,36 @@
|
||||
#include "cocoon.h"
|
||||
#include "http.h"
|
||||
#include "platform.h"
|
||||
#include "proxy_tls.h"
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
#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 */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user