feat(server): 实现基于 coco 协程的 TCP 服务器核心

- server_create/start/stop/destroy: 服务器生命周期管理
- accept_loop: 主 accept 循环(单线程/多线程模式)
- client_handler: 每个连接一个协程,支持 HTTP keep-alive
- conn_read/write: 协程安全的非阻塞 I/O 封装
- 支持多线程调度(coco_global_sched)
This commit is contained in:
xfy911 2026-06-03 16:47:40 +08:00
parent d7b9c93e78
commit da8fe6b545
2 changed files with 516 additions and 0 deletions

464
server.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#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);
}

52
server.h Normal file
View File

@ -0,0 +1,52 @@
/**
* server.h -
*
* @author xfy
*/
#ifndef COCOON_SERVER_H
#define COCOON_SERVER_H
#include "cocoon.h"
#include <stdint.h>
/**
* 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 */