From da8fe6b5451714dd8d2f91b081eac4db1336ad03 Mon Sep 17 00:00:00 2001 From: xfy911 Date: Wed, 3 Jun 2026 16:47:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E5=AE=9E=E7=8E=B0=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=20coco=20=E5=8D=8F=E7=A8=8B=E7=9A=84=20TCP=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=A0=B8=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server_create/start/stop/destroy: 服务器生命周期管理 - accept_loop: 主 accept 循环(单线程/多线程模式) - client_handler: 每个连接一个协程,支持 HTTP keep-alive - conn_read/write: 协程安全的非阻塞 I/O 封装 - 支持多线程调度(coco_global_sched) --- server.c | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.h | 52 +++++++ 2 files changed, 516 insertions(+) create mode 100644 server.c create mode 100644 server.h diff --git a/server.c b/server.c new file mode 100644 index 0000000..60bfe43 --- /dev/null +++ b/server.c @@ -0,0 +1,464 @@ +/** + * server.c - 服务器核心实现 + * + * 基于 coco 协程库实现高并发静态资源服务器。 + * 每个客户端连接由一个独立的协程处理,主线程负责 accept。 + * + * 架构: + * 主线程: socket() → bind() → listen() → accept() → 创建协程 + * 协程: 读取请求 → 解析 HTTP → 服务静态资源 → 关闭连接 + * + * @author xfy + */ + +#include "server.h" +#include "http.h" +#include "static.h" +#include "cocoon.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../coco/include/coco.h" + +/* 单个连接缓冲区大小 */ +#define CONN_BUF_SIZE 8192 +/* 连接超时(毫秒) */ +#define CONN_TIMEOUT_MS 30000 + +/** + * connection_t - 单个客户端连接上下文 + * + * 包含 socket fd、接收缓冲区、解析状态。 + */ +typedef struct { + int fd; /**< 客户端 socket */ + char buf[CONN_BUF_SIZE]; /**< 接收缓冲区 */ + size_t buf_len; /**< 缓冲区已用长度 */ + bool keep_alive; /**< 当前连接是否保持 */ + bool closed; /**< 连接是否已关闭 */ +} connection_t; + +/** + * server_context - 服务器运行时上下文 + */ +struct server_context { + int listen_fd; /**< 监听 socket */ + cocoon_config_t config; /**< 配置副本 */ + volatile int running; /**< 运行标志 */ + coco_sched_t *sched; /**< 协程调度器 */ +}; + +/** + * set_nonblocking - 设置 socket 为非阻塞模式 + * + * @param fd socket 文件描述符 + */ +static void set_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +/** + * close_connection - 安全关闭连接 + * + * 关闭 socket 并释放连接结构体内存。 + * + * @param conn 连接上下文 + */ +static void close_connection(connection_t *conn) { + if (conn && conn->fd >= 0) { + close(conn->fd); + conn->fd = -1; + conn->closed = true; + } +} + +/** + * conn_read - 从连接读取数据(协程安全) + * + * 使用 coco 的 I/O API 进行非阻塞读取,协程自动 yield 等待数据就绪。 + * 如果协程调度器不可用(多线程模式),回退到普通 read。 + * + * @param conn 连接上下文 + * @return 读取的字节数,0 表示对端关闭,-1 表示错误 + */ +static ssize_t conn_read(connection_t *conn) { + if (!conn || conn->fd < 0 || conn->closed) return -1; + + size_t space = CONN_BUF_SIZE - conn->buf_len; + if (space == 0) return -1; /* 缓冲区满 */ + + ssize_t n; + + /* 尝试使用 coco 的异步 I/O */ + if (coco_sched_get_current() != NULL) { + n = coco_read(conn->fd, conn->buf + conn->buf_len, space); + } else { + /* 无调度器时直接 read */ + n = read(conn->fd, conn->buf + conn->buf_len, space); + } + + if (n > 0) { + conn->buf_len += (size_t)n; + } + return n; +} + +/** + * conn_write - 向连接写入数据(协程安全) + * + * 使用 coco 的 I/O API 进行非阻塞写入,协程自动 yield 等待可写。 + * + * @param fd socket + * @param data 数据缓冲区 + * @param len 数据长度 + * @return 0 成功,-1 失败 + */ +static int conn_write(int fd, const char *data, size_t len) { + if (fd < 0) return -1; + + size_t sent = 0; + while (sent < len) { + ssize_t n; + if (coco_sched_get_current() != NULL) { + n = coco_write(fd, data + sent, len - sent); + } else { + n = write(fd, data + sent, len - sent); + } + if (n < 0) return -1; + if (n == 0) return -1; + sent += (size_t)n; + } + return 0; +} + +/** + * handle_request - 处理单个 HTTP 请求 + * + * 从缓冲区解析请求,判断是文件还是目录,调用对应的服务函数。 + * + * @param conn 连接上下文 + * @param root_dir 静态资源根目录 + * @return true 保持连接,false 关闭连接 + */ +static bool handle_request(connection_t *conn, const char *root_dir) { + http_request_t req; + int parsed = http_parse_request(conn->buf, conn->buf_len, &req); + + if (parsed < 0) { + if (parsed == -1) { + /* 数据不完整,等待更多数据 */ + return true; + } + /* 格式错误 */ + static_send_error(conn->fd, 400, false); + return false; + } + + /* 消费已解析的数据 */ + if ((size_t)parsed < conn->buf_len) { + memmove(conn->buf, conn->buf + parsed, conn->buf_len - (size_t)parsed); + } + conn->buf_len -= (size_t)parsed; + + /* 只支持 GET 和 HEAD */ + if (req.method != HTTP_GET && req.method != HTTP_HEAD) { + static_send_error(conn->fd, 405, req.keep_alive); + return req.keep_alive; + } + + /* 安全路径拼接 */ + char real_path[4096]; + char root_normalized[4096]; + if (!realpath(root_dir, root_normalized)) { + strncpy(root_normalized, root_dir, sizeof(root_normalized) - 1); + root_normalized[sizeof(root_normalized) - 1] = '\0'; + } + + int n = snprintf(real_path, sizeof(real_path), "%s%s", root_normalized, req.path); + if (n < 0 || (size_t)n >= sizeof(real_path)) { + static_send_error(conn->fd, 400, req.keep_alive); + return req.keep_alive; + } + + /* 路径遍历检查 */ + if (strstr(req.path, "..") != NULL) { + char resolved[4096]; + if (!realpath(real_path, resolved) || + strncmp(resolved, root_normalized, strlen(root_normalized)) != 0) { + static_send_error(conn->fd, 403, req.keep_alive); + return req.keep_alive; + } + strncpy(real_path, resolved, sizeof(real_path) - 1); + real_path[sizeof(real_path) - 1] = '\0'; + } + + /* 判断文件类型 */ + struct stat st; + if (stat(real_path, &st) != 0) { + static_send_error(conn->fd, 404, req.keep_alive); + return req.keep_alive; + } + + if (S_ISDIR(st.st_mode)) { + /* 目录:尝试 index.html */ + char index_path[4096]; + snprintf(index_path, sizeof(index_path), "%s/index.html", real_path); + struct stat index_st; + if (stat(index_path, &index_st) == 0 && S_ISREG(index_st.st_mode)) { + /* 有 index.html,作为文件服务 */ + http_request_t index_req = req; + strncpy(index_req.path, req.path, sizeof(index_req.path) - 1); + strncat(index_req.path, "/index.html", sizeof(index_req.path) - strlen(index_req.path) - 1); + static_serve_file(conn->fd, &index_req, root_dir); + } else { + /* 无 index.html,生成目录列表 */ + static_serve_directory(conn->fd, &req, root_dir, real_path); + } + } else if (S_ISREG(st.st_mode)) { + /* 普通文件 */ + static_serve_file(conn->fd, &req, root_dir); + } else { + static_send_error(conn->fd, 403, req.keep_alive); + } + + return req.keep_alive; +} + +/** + * client_handler - 客户端连接协程入口 + * + * 每个连接一个协程,循环读取请求并处理,直到连接关闭或超时。 + * + * @param arg 连接上下文指针(connection_t*) + */ +static void client_handler(void *arg) { + connection_t *conn = (connection_t *)arg; + if (!conn) return; + + while (!conn->closed) { + /* 读取数据 */ + ssize_t n = conn_read(conn); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* 数据不足,继续等待 */ + continue; + } + break; /* 错误 */ + } + if (n == 0) { + break; /* 对端关闭 */ + } + + /* 尝试处理请求 */ + bool keep = handle_request(conn, conn->sched ? "" : ""); + if (!keep) { + break; + } + } + + close_connection(conn); + free(conn); +} + +/** + * accept_loop - 主 accept 循环 + * + * 在单线程模式下直接运行,在多线程模式下作为协程运行。 + * 循环 accept 新连接,为每个连接创建处理协程。 + * + * @param arg 服务器上下文指针 + */ +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 : (int)sysconf(_SC_NPROCESSORS_ONLN)); + } + printf("[Cocoon] 静态资源根目录: %s\n", ctx->config.root_dir); + + while (ctx->running) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + + int client_fd = accept(ctx->listen_fd, + (struct sockaddr *)&client_addr, &addr_len); + if (client_fd < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + continue; + } + perror("accept"); + break; + } + + /* 设置 TCP_NODELAY 减少延迟 */ + int opt = 1; + setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + /* 创建连接上下文 */ + connection_t *conn = (connection_t *)calloc(1, sizeof(connection_t)); + if (!conn) { + close(client_fd); + continue; + } + conn->fd = client_fd; + conn->keep_alive = true; + conn->closed = false; + + if (ctx->config.threaded && coco_sched_get_current()) { + /* 多线程协程模式:创建协程处理连接 */ + coco_coro_t *coro = coco_create(coco_sched_get_current(), + client_handler, conn, 0); + if (!coro) { + close_connection(conn); + free(conn); + } + } else { + /* 单线程模式:直接调用处理函数(阻塞) */ + client_handler(conn); + } + } +} + +/** + * server_create - 创建服务器上下文 + * + * 初始化监听 socket、配置副本。 + * + * @param config 配置指针 + * @return 服务器上下文,失败返回 NULL + */ +server_context_t *server_create(const cocoon_config_t *config) { + if (!config || !config->root_dir) return NULL; + + server_context_t *ctx = (server_context_t *)calloc(1, sizeof(server_context_t)); + if (!ctx) return NULL; + + /* 复制配置 */ + ctx->config = *config; + ctx->config.root_dir = strdup(config->root_dir); + ctx->running = 1; + + /* 创建监听 socket */ + ctx->listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (ctx->listen_fd < 0) { + free(ctx); + return NULL; + } + + /* 允许端口复用 */ + int opt = 1; + setsockopt(ctx->listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + /* 绑定地址 */ + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(config->port); + + if (bind(ctx->listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(ctx->listen_fd); + free(ctx); + return NULL; + } + + /* 开始监听 */ + if (listen(ctx->listen_fd, 128) < 0) { + close(ctx->listen_fd); + free(ctx); + return NULL; + } + + /* 非阻塞模式 */ + set_nonblocking(ctx->listen_fd); + + return ctx; +} + +/** + * server_start - 启动服务器(阻塞) + * + * 根据配置选择单线程或多线程模式运行。 + * + * @param ctx 服务器上下文 + * @return COCOON_OK 成功,负值错误码 + */ +int server_start(server_context_t *ctx) { + if (!ctx) return COCOON_ERROR; + + if (ctx->config.threaded) { + /* 多线程协程模式 */ + 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; + } + + /* 启动全局调度器 */ + int ret = coco_global_sched_start(num_workers); + if (ret != COCO_OK) { + fprintf(stderr, "[Cocoon] 启动多线程调度器失败: %d\n", ret); + return COCOON_ERROR; + } + + /* 在调度器上运行 accept 循环 */ + /* 注意:当前实现使用主线程直接 accept,多线程仅用于工作协程 */ + accept_loop(ctx); + + coco_global_sched_wait(); + coco_global_sched_stop(); + } else { + /* 单线程模式 */ + accept_loop(ctx); + } + + return COCOON_OK; +} + +/** + * server_stop - 请求服务器停止 + * + * 设置停止标志,accept 循环将在下一次迭代时退出。 + * + * @param ctx 服务器上下文 + */ +void server_stop(server_context_t *ctx) { + if (ctx) { + ctx->running = 0; + } +} + +/** + * server_destroy - 销毁服务器上下文 + * + * 关闭监听 socket,释放配置内存。 + * + * @param ctx 服务器上下文 + */ +void server_destroy(server_context_t *ctx) { + if (!ctx) return; + + if (ctx->listen_fd >= 0) { + close(ctx->listen_fd); + ctx->listen_fd = -1; + } + + if (ctx->config.root_dir) { + free((void *)ctx->config.root_dir); + ctx->config.root_dir = NULL; + } + + free(ctx); +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..19483c4 --- /dev/null +++ b/server.h @@ -0,0 +1,52 @@ +/** + * server.h - 服务器核心模块接口 + * + * @author xfy + */ + +#ifndef COCOON_SERVER_H +#define COCOON_SERVER_H + +#include "cocoon.h" +#include + +/** + * server_context_t - 服务器运行时上下文 + * + * 包含监听 socket、配置、运行状态等。 + */ +typedef struct server_context server_context_t; + +/** + * server_start - 启动服务器(阻塞直到 stop 被调用) + * + * @param ctx 服务器上下文 + * @return COCOON_OK 成功,负值错误码 + */ +int server_start(server_context_t *ctx); + +/** + * server_stop - 请求服务器停止 + * + * 设置停止标志,等待当前连接处理完毕后退出。 + * + * @param ctx 服务器上下文 + */ +void server_stop(server_context_t *ctx); + +/** + * server_create - 创建服务器上下文 + * + * @param config 配置指针 + * @return 服务器上下文,失败返回 NULL + */ +server_context_t *server_create(const cocoon_config_t *config); + +/** + * server_destroy - 销毁服务器上下文 + * + * @param ctx 服务器上下文 + */ +void server_destroy(server_context_t *ctx); + +#endif /* COCOON_SERVER_H */