diff --git a/Makefile b/Makefile index 2aeb017..79196d9 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin # 源文件 -SRCS = main.c server.c http.c static.c +SRCS = main.c server.c http.c static.c log.c OBJS = $(SRCS:.c=.o) TARGET = cocoon diff --git a/cocoon.h b/cocoon.h index 29337bb..68495c5 100644 --- a/cocoon.h +++ b/cocoon.h @@ -13,7 +13,8 @@ #include #include -/* === 错误码 === */ +#include "log.h" + #define COCOON_OK 0 /**< 成功 */ #define COCOON_ERROR -1 /**< 通用错误 */ #define COCOON_NOMEM -2 /**< 内存不足 */ @@ -32,7 +33,9 @@ typedef struct cocoon_config { uint16_t port; /**< 监听端口 */ bool threaded; /**< 是否启用多线程调度 */ uint32_t num_workers; /**< 工作线程数(0 = 自动检测) */ - bool verbose; /**< 详细日志输出 */ + uint32_t max_connections; /**< 最大并发连接数(0 = 无限制) */ + uint32_t timeout_ms; /**< 连接空闲超时毫秒(0 = 默认30000) */ + log_level_t log_level; /**< 日志级别 */ } cocoon_config_t; /* === 服务器生命周期 API === */ diff --git a/http.c b/http.c index 2d16f80..5f1744d 100644 --- a/http.c +++ b/http.c @@ -66,6 +66,9 @@ static void parse_headers(const char **p, const char *end, http_request_t *req) req->range_end = -1; req->has_if_none_match = false; req->has_if_modified_since = false; + req->accept_gzip = false; + req->accept_deflate = false; + req->has_accept_encoding = false; while (*p < end && req->num_headers < HTTP_MAX_HEADERS) { const char *line_start = *p; @@ -122,7 +125,11 @@ static void parse_headers(const char **p, const char *end, http_request_t *req) if (copy_len >= 64) copy_len = 63; memcpy(req->if_modified_since, req->headers[req->num_headers].value, copy_len); req->if_modified_since[copy_len] = '\0'; - } else if (strcmp(req->headers[req->num_headers].name, "range") == 0) { + } else if (strcmp(req->headers[req->num_headers].name, "accept-encoding") == 0) { + req->has_accept_encoding = true; + const char *val = req->headers[req->num_headers].value; + if (strstr(val, "gzip") != NULL) req->accept_gzip = true; + if (strstr(val, "deflate") != NULL) req->accept_deflate = true; /* 解析 Range: bytes=start-end */ const char *range_val = req->headers[req->num_headers].value; if (strncasecmp(range_val, "bytes=", 6) == 0) { diff --git a/http.h b/http.h index e24c807..e8b0b47 100644 --- a/http.h +++ b/http.h @@ -54,6 +54,11 @@ typedef struct { char if_modified_since[64]; bool has_if_none_match; bool has_if_modified_since; + + /* 压缩相关头 */ + bool accept_gzip; /* 客户端支持 gzip */ + bool accept_deflate; /* 客户端支持 deflate */ + bool has_accept_encoding; } http_request_t; /* === HTTP 响应 === */ diff --git a/log.c b/log.c new file mode 100644 index 0000000..63716fe --- /dev/null +++ b/log.c @@ -0,0 +1,81 @@ +/** + * log.c - 简单日志系统实现 + * + * @author xfy + */ + +#include "log.h" +#include +#include +#include +#include + +static log_level_t g_level = LOG_LEVEL_INFO; +static const char *g_prefix = "[Cocoon]"; + +static const char *level_str(log_level_t level) { + switch (level) { + case LOG_LEVEL_ERROR: return "ERROR"; + case LOG_LEVEL_WARN: return "WARN"; + case LOG_LEVEL_INFO: return "INFO"; + case LOG_LEVEL_DEBUG: return "DEBUG"; + default: return "UNKNOWN"; + } +} + +void log_set_level(log_level_t level) { + g_level = level; +} + +void log_set_prefix(const char *prefix) { + g_prefix = prefix; +} + +log_level_t log_get_level(void) { + return g_level; +} + +static void log_output(log_level_t level, const char *fmt, va_list args) { + if (level > g_level) return; + + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + char time_buf[32]; + strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info); + + if (g_prefix) { + fprintf(stderr, "%s %s [%s] ", time_buf, g_prefix, level_str(level)); + } else { + fprintf(stderr, "%s [%s] ", time_buf, level_str(level)); + } + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void log_error(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_output(LOG_LEVEL_ERROR, fmt, args); + va_end(args); +} + +void log_warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_output(LOG_LEVEL_WARN, fmt, args); + va_end(args); +} + +void log_info(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_output(LOG_LEVEL_INFO, fmt, args); + va_end(args); +} + +void log_debug(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_output(LOG_LEVEL_DEBUG, fmt, args); + va_end(args); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..a89a6c6 --- /dev/null +++ b/log.h @@ -0,0 +1,55 @@ +/** + * log.h - 简单日志系统 + * + * 支持分级日志输出:ERROR、WARN、INFO、DEBUG。 + * 线程安全(使用 printf,底层为线程安全)。 + * + * @author xfy + */ + +#ifndef COCOON_LOG_H +#define COCOON_LOG_H + +#include + +/* === 日志级别 === */ +typedef enum { + LOG_LEVEL_ERROR = 0, /**< 错误,程序可能无法继续 */ + LOG_LEVEL_WARN = 1, /**< 警告,程序可继续但需注意 */ + LOG_LEVEL_INFO = 2, /**< 信息,正常运行输出 */ + LOG_LEVEL_DEBUG = 3, /**< 调试,详细运行状态 */ +} log_level_t; + +/** + * log_set_level - 设置全局日志级别 + * + * 低于此级别的日志将被忽略。 + * + * @param level 最低输出级别 + */ +void log_set_level(log_level_t level); + +/** + * log_set_prefix - 设置日志前缀(默认 [Cocoon]) + * + * @param prefix 前缀字符串(如 "[Cocoon]"),NULL 表示无前缀 + */ +void log_set_prefix(const char *prefix); + +/** + * log_get_level - 获取当前日志级别 + * @return 当前日志级别 + */ +log_level_t log_get_level(void); + +/** + * log_error / log_warn / log_info / log_debug - 分级日志输出 + * + * 使用 printf 风格格式化字符串,自动追加换行。 + */ +void log_error(const char *fmt, ...); +void log_warn(const char *fmt, ...); +void log_info(const char *fmt, ...); +void log_debug(const char *fmt, ...); + +#endif /* COCOON_LOG_H */ diff --git a/main.c b/main.c index 6a97bfc..2175a41 100644 --- a/main.c +++ b/main.c @@ -45,7 +45,10 @@ static void print_usage(const char *prog) { printf(" -p 监听端口(默认 8080)\n"); printf(" -t 启用多线程调度\n"); printf(" -w 工作线程数(默认自动检测 CPU 核心)\n"); - printf(" -v 详细日志输出\n"); + printf(" -m 最大并发连接数(默认无限制)\n"); + printf(" -o 连接空闲超时毫秒(默认 30000)\n"); + printf(" -l 日志级别: error, warn, info, debug(默认 info)\n"); + printf(" -v 详细日志输出(等同于 -l debug)\n"); printf(" -h 显示此帮助\n"); printf("\nExample:\n"); printf(" %s -r ./www -p 8080\n", prog); @@ -66,7 +69,9 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) { config->port = 8080; config->threaded = false; config->num_workers = 0; - config->verbose = false; + config->max_connections = 0; + config->timeout_ms = 0; + config->log_level = LOG_LEVEL_INFO; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-r") == 0) { @@ -81,8 +86,24 @@ static bool parse_args(int argc, char *argv[], cocoon_config_t *config) { } else if (strcmp(argv[i], "-w") == 0) { if (++i >= argc) return false; config->num_workers = (uint32_t)atoi(argv[i]); + } else if (strcmp(argv[i], "-m") == 0) { + if (++i >= argc) return false; + config->max_connections = (uint32_t)atoi(argv[i]); + } else if (strcmp(argv[i], "-o") == 0) { + if (++i >= argc) return false; + config->timeout_ms = (uint32_t)atoi(argv[i]); + } else if (strcmp(argv[i], "-l") == 0) { + if (++i >= argc) return false; + if (strcmp(argv[i], "error") == 0) config->log_level = LOG_LEVEL_ERROR; + else if (strcmp(argv[i], "warn") == 0) config->log_level = LOG_LEVEL_WARN; + else if (strcmp(argv[i], "info") == 0) config->log_level = LOG_LEVEL_INFO; + else if (strcmp(argv[i], "debug") == 0) config->log_level = LOG_LEVEL_DEBUG; + else { + fprintf(stderr, "Unknown log level: %s\n", argv[i]); + return false; + } } else if (strcmp(argv[i], "-v") == 0) { - config->verbose = true; + config->log_level = LOG_LEVEL_DEBUG; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { print_usage(argv[0]); exit(0); @@ -121,6 +142,9 @@ int main(int argc, char *argv[]) { signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); + /* 设置日志级别 */ + log_set_level(config.log_level); + /* 创建服务器 */ g_ctx = server_create(&config); if (!g_ctx) { diff --git a/server.c b/server.c index abc3d3c..b4519a4 100644 --- a/server.c +++ b/server.c @@ -10,6 +10,11 @@ * 主线程: socket() → bind() → listen() → accept() → 创建协程 * 协程: 读取请求 → 解析 HTTP → 服务静态资源 → 关闭连接 * + * 新增功能(2026-06-03): + * - 连接空闲超时管理(自动清理僵尸连接) + * - 最大并发连接数限制(防止资源耗尽) + * - 分级日志输出(替代 printf) + * * @author xfy */ @@ -17,6 +22,7 @@ #include "http.h" #include "static.h" #include "cocoon.h" +#include "log.h" #include #include #include @@ -28,31 +34,31 @@ #include #include #include +#include #include "../coco/include/coco.h" /* 单个连接缓冲区大小 */ #define CONN_BUF_SIZE 8192 -/* 连接超时(毫秒) */ +/* 默认连接超时(毫秒) */ #define CONN_TIMEOUT_MS 30000 /** * connection_t - 单个客户端连接上下文 * - * 包含 socket fd、接收缓冲区、解析状态。 + * 包含 socket fd、接收缓冲区、解析状态、超时管理。 */ typedef struct { - int fd; /**< 客户端 socket */ - char buf[CONN_BUF_SIZE]; /**< 接收缓冲区 */ - size_t buf_len; /**< 缓冲区已用长度 */ - bool keep_alive; /**< 当前连接是否保持 */ - bool closed; /**< 连接是否已关闭 */ - const char *root_dir; /**< 静态资源根目录(引用,不拥有) */ + int fd; /**< 客户端 socket */ + char buf[CONN_BUF_SIZE]; /**< 接收缓冲区 */ + size_t buf_len; /**< 缓冲区已用长度 */ + bool keep_alive; /**< 当前连接是否保持 */ + bool closed; /**< 连接是否已关闭 */ + const char *root_dir; /**< 静态资源根目录(引用,不拥有) */ + uint32_t timeout_ms; /**< 连接空闲超时毫秒(从配置复制) */ + coco_timer_t *timer; /**< 空闲超时定时器 */ + coco_coro_t *coro; /**< 当前处理协程 */ } connection_t; - -/** - * server_context - 服务器运行时上下文 - */ struct server_context { int listen_fd; /**< 监听 socket */ cocoon_config_t config; /**< 配置副本 */ @@ -60,6 +66,9 @@ struct server_context { coco_sched_t *sched; /**< 协程调度器 */ }; +/* 全局活跃连接计数器(线程安全) */ +static atomic_int g_active_connections = 0; + /** * set_nonblocking - 设置 socket 为非阻塞模式 * @@ -73,7 +82,7 @@ static void set_nonblocking(int fd) { /** * close_connection - 安全关闭连接 * - * 关闭 socket 并释放连接结构体内存。 + * 关闭 socket,递减活跃连接计数。 * * @param conn 连接上下文 */ @@ -82,6 +91,7 @@ static void close_connection(connection_t *conn) { close(conn->fd); conn->fd = -1; conn->closed = true; + atomic_fetch_sub(&g_active_connections, 1); } } @@ -213,6 +223,59 @@ static bool handle_request(connection_t *conn, const char *root_dir) { return req.keep_alive; } +/** + * conn_timeout_handler - 连接空闲超时回调 + * + * 定时器触发时关闭 socket,唤醒阻塞在 coco_read 的协程, + * 并发起协程取消请求。 + * + * @param arg 连接上下文指针 + */ +static void conn_timeout_handler(void *arg) { + connection_t *conn = (connection_t *)arg; + if (!conn) return; + + conn->timer = NULL; /* 定时器已触发,自动释放 */ + + if (conn->fd >= 0) { + log_debug("连接 fd=%d 空闲超时,强制关闭", conn->fd); + conn->closed = true; + /* shutdown 唤醒阻塞在 coco_read 的协程 */ + shutdown(conn->fd, SHUT_RDWR); + } + + if (conn->coro) { + coco_cancel(conn->coro); + } +} + +/** + * conn_reset_timer - 重置连接空闲定时器 + * + * 每次收到数据时调用,取消旧定时器并创建新定时器。 + * + * @param conn 连接上下文 + * @param timeout_ms 超时毫秒数 + */ +static void conn_reset_timer(connection_t *conn, uint32_t timeout_ms) { + if (conn->timer) { + coco_timer_cancel(conn->timer); + } + conn->timer = coco_timer(timeout_ms, conn_timeout_handler, conn); +} + +/** + * conn_cancel_timer - 取消连接定时器 + * + * @param conn 连接上下文 + */ +static void conn_cancel_timer(connection_t *conn) { + if (conn->timer) { + coco_timer_cancel(conn->timer); + conn->timer = NULL; + } +} + /** * client_handler - 客户端连接协程入口 * @@ -224,6 +287,12 @@ static void client_handler(void *arg) { connection_t *conn = (connection_t *)arg; if (!conn) return; + conn->coro = coco_self(); + + /* 启动空闲定时器 */ + uint32_t timeout_ms = conn->timeout_ms > 0 ? conn->timeout_ms : CONN_TIMEOUT_MS; + conn->timer = coco_timer(timeout_ms, conn_timeout_handler, conn); + while (!conn->closed) { /* 读取数据 */ ssize_t n = conn_read(conn); @@ -232,12 +301,16 @@ static void client_handler(void *arg) { /* 数据不足,继续等待 */ continue; } - break; /* 错误 */ + /* coco_read 返回负值错误码(如 COCO_ERROR_CANCELLED)或系统错误 */ + break; } if (n == 0) { break; /* 对端关闭 */ } + /* 有数据读取,重置定时器 */ + conn_reset_timer(conn, timeout_ms); + /* 尝试处理请求 */ bool keep = handle_request(conn, conn->root_dir); if (!keep) { @@ -245,6 +318,7 @@ static void client_handler(void *arg) { } } + conn_cancel_timer(conn); close_connection(conn); free(conn); } @@ -261,12 +335,24 @@ static void accept_loop(void *arg) { server_context_t *ctx = (server_context_t *)arg; if (!ctx) return; - printf("[Cocoon] 服务器启动于端口 %d\n", ctx->config.port); - if (ctx->config.threaded) { - printf("[Cocoon] 多线程模式: %d 个工作线程\n", - ctx->config.num_workers > 0 ? ctx->config.num_workers : (uint32_t)sysconf(_SC_NPROCESSORS_ONLN)); + uint32_t num_workers = ctx->config.num_workers; + if (num_workers == 0) { + num_workers = (uint32_t)sysconf(_SC_NPROCESSORS_ONLN); + if (num_workers == 0) num_workers = 4; } - printf("[Cocoon] 静态资源根目录: %s\n", ctx->config.root_dir); + + log_info("服务器启动于端口 %d", ctx->config.port); + if (ctx->config.threaded) { + log_info("多线程模式: %d 个工作线程", num_workers); + } else { + log_info("单线程模式"); + } + log_info("静态资源根目录: %s", ctx->config.root_dir); + + if (ctx->config.max_connections > 0) { + log_info("最大并发连接数: %u", ctx->config.max_connections); + } + log_info("连接空闲超时: %u ms", ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS); while (ctx->running) { struct sockaddr_in client_addr; @@ -278,10 +364,27 @@ static void accept_loop(void *arg) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; } - perror("accept"); + log_error("accept 失败: %s", strerror(errno)); break; } + /* 检查最大连接数限制 */ + if (ctx->config.max_connections > 0) { + int current = atomic_load(&g_active_connections); + if (current >= (int)ctx->config.max_connections) { + log_warn("连接数已达上限 (%d/%u),拒绝新连接 fd=%d", + current, ctx->config.max_connections, client_fd); + /* 发送 503 后关闭 */ + const char *resp = "HTTP/1.1 503 Service Unavailable\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n"; + ssize_t wr = write(client_fd, resp, strlen(resp)); + (void)wr; + close(client_fd); + continue; + } + } + /* 设置 TCP_NODELAY 减少延迟 */ int opt = 1; setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); @@ -296,12 +399,19 @@ static void accept_loop(void *arg) { conn->keep_alive = true; conn->closed = false; conn->root_dir = ctx->config.root_dir; + conn->timeout_ms = ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS; + + atomic_fetch_add(&g_active_connections, 1); + log_debug("新连接 fd=%d,当前活跃连接: %d", client_fd, + atomic_load(&g_active_connections)); if (ctx->config.threaded && coco_sched_get_current()) { /* 多线程协程模式:创建协程处理连接 */ coco_coro_t *coro = coco_create(coco_sched_get_current(), client_handler, conn, 0); if (!coro) { + log_error("创建协程失败,关闭连接 fd=%d", client_fd); + atomic_fetch_sub(&g_active_connections, 1); close_connection(conn); free(conn); } @@ -390,7 +500,7 @@ int server_start(server_context_t *ctx) { /* 启动全局调度器 */ int ret = coco_global_sched_start(num_workers); if (ret != COCO_OK) { - fprintf(stderr, "[Cocoon] 启动多线程调度器失败: %d\n", ret); + log_error("启动多线程调度器失败: %d", ret); return COCOON_ERROR; }