强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 完整项目:构建一个 HTTP 服务器

完整项目:构建一个 HTTP 服务器

本教程从零开始,用纯 C 语言构建一个支持 HTTP/1.1 的高性能 Web 服务器。涵盖 Socket 编程、epoll 事件循环、HTTP 解析、路由系统、日志、配置、信号处理和性能优化。

项目架构设计

目录结构

http-server/
├── CMakeLists.txt
├── config.ini
├── include/
│   ├── server.h          # 服务器核心
│   ├── http.h            # HTTP 解析
│   ├── router.h          # 路由系统
│   ├── logger.h          # 日志系统
│   ├── config.h          # 配置解析
│   └── pool.h            # 线程池
├── src/
│   ├── main.c
│   ├── server.c
│   ├── http.c
│   ├── router.c
│   ├── logger.c
│   ├── config.c
│   └── pool.c
├── static/
│   └── index.html
└── test/
    └── test_http.c

模块关系

main.c
  ├── config.c   (读取配置)
  ├── logger.c   (初始化日志)
  ├── router.c   (注册路由)
  ├── pool.c     (创建线程池)
  └── server.c   (启动服务器)
        ├── epoll 事件循环
        ├── 接受连接
        └── http.c (解析请求、生成响应)

Socket 服务器

TCP Socket 基础

// include/server.h
#ifndef SERVER_H
#define SERVER_H

#include <stdint.h>
#include <stdbool.h>

#define MAX_EVENTS 1024
#define BUFFER_SIZE 8192

typedef struct {
    int listen_fd;
    int epoll_fd;
    uint16_t port;
    bool running;
    int thread_count;
} Server;

// 服务器生命周期
int server_init(Server *srv, uint16_t port, int threads);
int server_start(Server *srv);
void server_stop(Server *srv);
void server_destroy(Server *srv);

#endif
// src/server.c
#include "server.h"
#include "http.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <signal.h>

// 设置 socket 为非阻塞
static int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// 创建监听 socket
static int create_listen_socket(uint16_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    // 允许端口复用
    int opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &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(port);

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(fd);
        return -1;
    }

    if (listen(fd, SOMAXCONN) < 0) {
        perror("listen");
        close(fd);
        return -1;
    }

    set_nonblocking(fd);
    return fd;
}

int server_init(Server *srv, uint16_t port, int threads) {
    srv->port = port;
    srv->running = false;
    srv->thread_count = threads;

    // 忽略 SIGPIPE(写入已关闭的连接时不会崩溃)
    signal(SIGPIPE, SIG_IGN);

    // 创建监听 socket
    srv->listen_fd = create_listen_socket(port);
    if (srv->listen_fd < 0) return -1;

    // 创建 epoll 实例
    srv->epoll_fd = epoll_create1(0);
    if (srv->epoll_fd < 0) {
        perror("epoll_create1");
        close(srv->listen_fd);
        return -1;
    }

    // 将监听 socket 加入 epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = srv->listen_fd;
    if (epoll_ctl(srv->epoll_fd, EPOLL_CTL_ADD, srv->listen_fd, &ev) < 0) {
        perror("epoll_ctl");
        close(srv->epoll_fd);
        close(srv->listen_fd);
        return -1;
    }

    return 0;
}

epoll 事件循环

// 接受新连接
static int accept_connection(Server *srv) {
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    int client_fd = accept(srv->listen_fd, 
                           (struct sockaddr *)&client_addr, &addr_len);
    if (client_fd < 0) {
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("accept");
        }
        return -1;
    }

    set_nonblocking(client_fd);

    // 启用 TCP_NODELAY(禁用 Nagle 算法)
    int opt = 1;
    setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));

    // 加入 epoll
    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;  // 边缘触发
    ev.data.fd = client_fd;
    if (epoll_ctl(srv->epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) {
        perror("epoll_ctl add client");
        close(client_fd);
        return -1;
    }

    return client_fd;
}

// 处理客户端请求
static void handle_client(Server *srv, int client_fd) {
    char buffer[BUFFER_SIZE];
    ssize_t n = read(client_fd, buffer, sizeof(buffer) - 1);

    if (n <= 0) {
        if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
            return;  // 暂时无数据
        }
        // 连接关闭或错误
        epoll_ctl(srv->epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
        close(client_fd);
        return;
    }

    buffer[n] = '\0';

    // 解析 HTTP 请求
    HttpRequest req;
    if (http_parse_request(buffer, n, &req) < 0) {
        const char *bad_request = 
            "HTTP/1.1 400 Bad Request\r\n"
            "Content-Length: 11\r\n\r\n"
            "Bad Request";
        write(client_fd, bad_request, strlen(bad_request));
        epoll_ctl(srv->epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
        close(client_fd);
        return;
    }

    // 路由处理
    HttpResponse resp;
    http_response_init(&resp);
    router_handle(&req, &resp);

    // 发送响应
    char *resp_str;
    int resp_len = http_serialize_response(&resp, &resp_str);
    if (resp_len > 0) {
        write(client_fd, resp_str, resp_len);
        free(resp_str);
    }

    http_request_free(&req);
    http_response_free(&resp);

    // HTTP/1.1 keep-alive 处理
    if (req.keep_alive) {
        // 保持连接,等待下一个请求
    } else {
        epoll_ctl(srv->epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
        close(client_fd);
    }
}

// 主事件循环
int server_start(Server *srv) {
    srv->running = true;
    struct epoll_event events[MAX_EVENTS];

    printf("Server listening on port %d\n", srv->port);

    while (srv->running) {
        int nfds = epoll_wait(srv->epoll_fd, events, MAX_EVENTS, 1000);
        if (nfds < 0) {
            if (errno == EINTR) continue;
            perror("epoll_wait");
            break;
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == srv->listen_fd) {
                // 新连接
                while (accept_connection(srv) >= 0) {
                    // 持续接受直到 EAGAIN
                }
            } else {
                // 客户端数据
                handle_client(srv, events[i].data.fd);
            }
        }
    }

    return 0;
}

void server_stop(Server *srv) {
    srv->running = false;
}

void server_destroy(Server *srv) {
    if (srv->epoll_fd >= 0) close(srv->epoll_fd);
    if (srv->listen_fd >= 0) close(srv->listen_fd);
}

HTTP 请求解析

// include/http.h
#ifndef HTTP_H
#define HTTP_H

#include <stdbool.h>

#define HTTP_MAX_METHOD 8
#define HTTP_MAX_PATH 2048
#define HTTP_MAX_HEADERS 64
#define HTTP_MAX_BODY_SIZE (1024 * 1024)  // 1MB

typedef enum {
    HTTP_GET,
    HTTP_POST,
    HTTP_HEAD,
    HTTP_PUT,
    HTTP_DELETE,
    HTTP_OPTIONS,
    HTTP_METHOD_UNKNOWN
} HttpMethod;

typedef struct {
    char *name;
    char *value;
} HttpHeader;

typedef struct {
    HttpMethod method;
    char path[HTTP_MAX_PATH];
    char query_string[HTTP_MAX_PATH];
    int version_major;
    int version_minor;
    HttpHeader headers[HTTP_MAX_HEADERS];
    int header_count;
    char *body;
    int body_length;
    bool keep_alive;
} HttpRequest;

typedef struct {
    int status_code;
    char *status_text;
    HttpHeader headers[HTTP_MAX_HEADERS];
    int header_count;
    char *body;
    int body_length;
} HttpResponse;

// HTTP 解析
int http_parse_request(const char *data, int len, HttpRequest *req);
void http_request_free(HttpRequest *req);

// HTTP 响应
void http_response_init(HttpResponse *resp);
void http_response_set_status(HttpResponse *resp, int code);
void http_response_set_header(HttpResponse *resp, const char *name, const char *value);
void http_response_set_body(HttpResponse *resp, const char *body, int length);
int http_serialize_response(HttpResponse *resp, char **out);
void http_response_free(HttpResponse *resp);

// HTTP 方法字符串
const char *http_method_str(HttpMethod method);

#endif
// src/http.c
#include "http.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

static HttpMethod parse_method(const char *str) {
    if (strcmp(str, "GET") == 0) return HTTP_GET;
    if (strcmp(str, "POST") == 0) return HTTP_POST;
    if (strcmp(str, "HEAD") == 0) return HTTP_HEAD;
    if (strcmp(str, "PUT") == 0) return HTTP_PUT;
    if (strcmp(str, "DELETE") == 0) return HTTP_DELETE;
    if (strcmp(str, "OPTIONS") == 0) return HTTP_OPTIONS;
    return HTTP_METHOD_UNKNOWN;
}

const char *http_method_str(HttpMethod method) {
    switch (method) {
        case HTTP_GET:     return "GET";
        case HTTP_POST:    return "POST";
        case HTTP_HEAD:    return "HEAD";
        case HTTP_PUT:     return "PUT";
        case HTTP_DELETE:  return "DELETE";
        case HTTP_OPTIONS: return "OPTIONS";
        default:           return "UNKNOWN";
    }
}

int http_parse_request(const char *data, int len, HttpRequest *req) {
    memset(req, 0, sizeof(HttpRequest));
    req->keep_alive = true;  // HTTP/1.1 默认 keep-alive

    const char *line_end;
    const char *ptr = data;
    const char *data_end = data + len;

    // 解析请求行: METHOD PATH HTTP/1.x
    line_end = memchr(ptr, '\r', data_end - ptr);
    if (!line_end || line_end + 1 >= data_end || *(line_end + 1) != '\n') {
        return -1;  // 无效的行终止
    }

    char method_str[HTTP_MAX_METHOD];
    char path[HTTP_MAX_PATH];
    int major, minor;

    if (sscanf(ptr, "%7s %2047s HTTP/%d.%d",
               method_str, path, &major, &minor) != 4) {
        return -1;
    }

    req->method = parse_method(method_str);
    if (req->method == HTTP_METHOD_UNKNOWN) return -1;

    // 分离路径和查询字符串
    char *qmark = strchr(path, '?');
    if (qmark) {
        *qmark = '\0';
        strncpy(req->query_string, qmark + 1, HTTP_MAX_PATH - 1);
    }
    strncpy(req->path, path, HTTP_MAX_PATH - 1);

    req->version_major = major;
    req->version_minor = minor;

    ptr = line_end + 2;  // 跳过 \r\n

    // 解析头部
    while (ptr < data_end) {
        line_end = memchr(ptr, '\r', data_end - ptr);
        if (!line_end || line_end + 1 >= data_end) break;
        if (line_end == ptr) {
            ptr += 2;  // 空行,头部结束
            break;
        }

        if (req->header_count >= HTTP_MAX_HEADERS) break;

        char *colon = memchr(ptr, ':', line_end - ptr);
        if (!colon) return -1;

        // 提取 name
        int name_len = colon - ptr;
        req->headers[req->header_count].name = malloc(name_len + 1);
        memcpy(req->headers[req->header_count].name, ptr, name_len);
        req->headers[req->header_count].name[name_len] = '\0';

        // 跳过冒号和空格
        colon++;
        while (colon < line_end && *colon == ' ') colon++;

        // 提取 value
        int value_len = line_end - colon;
        req->headers[req->header_count].value = malloc(value_len + 1);
        memcpy(req->headers[req->header_count].value, colon, value_len);
        req->headers[req->header_count].value[value_len] = '\0';

        // 检查 Connection 头
        if (strcasecmp(req->headers[req->header_count].name, "Connection") == 0) {
            if (strcasecmp(req->headers[req->header_count].value, "close") == 0) {
                req->keep_alive = false;
            }
        }

        req->header_count++;
        ptr = line_end + 2;
    }

    // 解析 body
    if (ptr < data_end) {
        int content_length = 0;
        for (int i = 0; i < req->header_count; i++) {
            if (strcasecmp(req->headers[i].name, "Content-Length") == 0) {
                content_length = atoi(req->headers[i].value);
                break;
            }
        }
        if (content_length > 0 && content_length <= HTTP_MAX_BODY_SIZE) {
            req->body = malloc(content_length + 1);
            int available = data_end - ptr;
            int copy_len = (available < content_length) ? available : content_length;
            memcpy(req->body, ptr, copy_len);
            req->body[copy_len] = '\0';
            req->body_length = copy_len;
        }
    }

    return 0;
}

void http_request_free(HttpRequest *req) {
    for (int i = 0; i < req->header_count; i++) {
        free(req->headers[i].name);
        free(req->headers[i].value);
    }
    free(req->body);
}

void http_response_init(HttpResponse *resp) {
    memset(resp, 0, sizeof(HttpResponse));
    resp->status_code = 200;
    resp->status_text = "OK";
}

void http_response_set_status(HttpResponse *resp, int code) {
    resp->status_code = code;
    switch (code) {
        case 200: resp->status_text = "OK"; break;
        case 301: resp->status_text = "Moved Permanently"; break;
        case 304: resp->status_text = "Not Modified"; break;
        case 400: resp->status_text = "Bad Request"; break;
        case 403: resp->status_text = "Forbidden"; break;
        case 404: resp->status_text = "Not Found"; break;
        case 500: resp->status_text = "Internal Server Error"; break;
        default:  resp->status_text = "Unknown"; break;
    }
}

void http_response_set_header(HttpResponse *resp, const char *name, const char *value) {
    if (resp->header_count >= HTTP_MAX_HEADERS) return;
    resp->headers[resp->header_count].name = strdup(name);
    resp->headers[resp->header_count].value = strdup(value);
    resp->header_count++;
}

void http_response_set_body(HttpResponse *resp, const char *body, int length) {
    resp->body = malloc(length);
    memcpy(resp->body, body, length);
    resp->body_length = length;
}

int http_serialize_response(HttpResponse *resp, char **out) {
    // 计算需要的缓冲区大小
    int size = 64;  // 状态行 + 头部前的开销
    for (int i = 0; i < resp->header_count; i++) {
        size += strlen(resp->headers[i].name) + 
                strlen(resp->headers[i].value) + 4;  // ": " + "\r\n"
    }
    size += 2;  // 空行
    size += resp->body_length;

    *out = malloc(size + 1);
    char *ptr = *out;

    // 状态行
    int n = sprintf(ptr, "HTTP/1.1 %d %s\r\n", 
                    resp->status_code, resp->status_text);
    ptr += n;

    // 头部
    for (int i = 0; i < resp->header_count; i++) {
        n = sprintf(ptr, "%s: %s\r\n",
                    resp->headers[i].name, resp->headers[i].value);
        ptr += n;
    }

    // 空行
    *ptr++ = '\r';
    *ptr++ = '\n';

    // Body
    if (resp->body && resp->body_length > 0) {
        memcpy(ptr, resp->body, resp->body_length);
        ptr += resp->body_length;
    }

    return ptr - *out;
}

void http_response_free(HttpResponse *resp) {
    for (int i = 0; i < resp->header_count; i++) {
        free(resp->headers[i].name);
        free(resp->headers[i].value);
    }
    free(resp->body);
}

路由系统

// include/router.h
#ifndef ROUTER_H
#define ROUTER_H

#include "http.h"

typedef void (*RouteHandler)(HttpRequest *req, HttpResponse *resp);

typedef struct {
    HttpMethod method;
    char pattern[256];
    RouteHandler handler;
} Route;

#define MAX_ROUTES 128

// 路由注册
void router_init(void);
void router_add(HttpMethod method, const char *pattern, RouteHandler handler);
void router_handle(HttpRequest *req, HttpResponse *resp);

#endif
// src/router.c
#include "router.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

static Route routes[MAX_ROUTES];
static int route_count = 0;
static char static_root[256] = "./static";

// 默认路由处理器
static void handle_404(HttpRequest *req, HttpResponse *resp) {
    http_response_set_status(resp, 404);
    const char *body = "<h1>404 Not Found</h1>";
    http_response_set_body(resp, body, strlen(body));
    http_response_set_header(resp, "Content-Type", "text/html");
}

// 静态文件服务
static void serve_static_file(HttpRequest *req, HttpResponse *resp) {
    char filepath[512];
    snprintf(filepath, sizeof(filepath), "%s%s", static_root, req->path);

    // 安全检查: 防止目录遍历
    if (strstr(req->path, "..")) {
        http_response_set_status(resp, 403);
        const char *body = "<h1>403 Forbidden</h1>";
        http_response_set_body(resp, body, strlen(body));
        return;
    }

    struct stat st;
    if (stat(filepath, &st) < 0 || S_ISDIR(st.st_mode)) {
        // 尝试 index.html
        snprintf(filepath, sizeof(filepath), "%s%s/index.html", static_root, req->path);
        if (stat(filepath, &st) < 0) {
            handle_404(req, resp);
            return;
        }
    }

    FILE *f = fopen(filepath, "rb");
    if (!f) {
        handle_404(req, resp);
        return;
    }

    char *content = malloc(st.st_size);
    fread(content, 1, st.st_size, f);
    fclose(f);

    http_response_set_body(resp, content, st.st_size);
    free(content);

    // 设置 Content-Type
    const char *ext = strrchr(filepath, '.');
    const char *type = "application/octet-stream";
    if (ext) {
        if (strcmp(ext, ".html") == 0) type = "text/html";
        else if (strcmp(ext, ".css") == 0) type = "text/css";
        else if (strcmp(ext, ".js") == 0) type = "application/javascript";
        else if (strcmp(ext, ".json") == 0) type = "application/json";
        else if (strcmp(ext, ".png") == 0) type = "image/png";
        else if (strcmp(ext, ".jpg") == 0) type = "image/jpeg";
        else if (strcmp(ext, ".gif") == 0) type = "image/gif";
        else if (strcmp(ext, ".svg") == 0) type = "image/svg+xml";
    }
    http_response_set_header(resp, "Content-Type", type);

    // 设置 Content-Length
    char len_str[32];
    snprintf(len_str, sizeof(len_str), "%ld", (long)st.st_size);
    http_response_set_header(resp, "Content-Length", len_str);
}

// API 路由示例: /api/hello
static void handle_api_hello(HttpRequest *req, HttpResponse *resp) {
    const char *body = "{\"message\": \"Hello, World!\"}";
    http_response_set_body(resp, body, strlen(body));
    http_response_set_header(resp, "Content-Type", "application/json");
}

// API 路由示例: /api/echo (POST)
static void handle_api_echo(HttpRequest *req, HttpResponse *resp) {
    if (req->method != HTTP_POST) {
        http_response_set_status(resp, 405);
        const char *body = "{\"error\": \"Method Not Allowed\"}";
        http_response_set_body(resp, body, strlen(body));
        http_response_set_header(resp, "Content-Type", "application/json");
        return;
    }

    if (req->body) {
        http_response_set_body(resp, req->body, req->body_length);
        http_response_set_header(resp, "Content-Type", "application/json");
    } else {
        const char *body = "{\"error\": \"No body\"}";
        http_response_set_body(resp, body, strlen(body));
        http_response_set_header(resp, "Content-Type", "application/json");
    }
}

void router_init(void) {
    route_count = 0;

    // 注册 API 路由
    router_add(HTTP_GET, "/api/hello", handle_api_hello);
    router_add(HTTP_POST, "/api/echo", handle_api_echo);
}

void router_add(HttpMethod method, const char *pattern, RouteHandler handler) {
    if (route_count >= MAX_ROUTES) return;
    routes[route_count].method = method;
    strncpy(routes[route_count].pattern, pattern, 255);
    routes[route_count].handler = handler;
    route_count++;
}

void router_handle(HttpRequest *req, HttpResponse *resp) {
    // 添加通用响应头
    http_response_set_header(resp, "Server", "MyHTTPServer/1.0");
    http_response_set_header(resp, "Connection", 
                             req->keep_alive ? "keep-alive" : "close");

    // 查找匹配的路由
    for (int i = 0; i < route_count; i++) {
        if (routes[i].method == req->method &&
            strcmp(routes[i].pattern, req->path) == 0) {
            routes[i].handler(req, resp);

            // HEAD 请求不应返回 body
            if (req->method == HTTP_HEAD) {
                resp->body_length = 0;
                free(resp->body);
                resp->body = NULL;
            }
            return;
        }
    }

    // 没有匹配的 API 路由,尝试静态文件
    serve_static_file(req, resp);
}

日志系统

// include/logger.h
#ifndef LOGGER_H
#define LOGGER_H

typedef enum {
    LOG_DEBUG,
    LOG_INFO,
    LOG_WARN,
    LOG_ERROR,
    LOG_FATAL
} LogLevel;

int logger_init(const char *logfile, LogLevel level);
void logger_close(void);
void logger_log(LogLevel level, const char *file, int line, const char *fmt, ...);

#define LOG_DEBUG(...) logger_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...)  logger_log(LOG_INFO,  __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...)  logger_log(LOG_WARN,  __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) logger_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_FATAL(...) logger_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)

#endif
// src/logger.c
#include "logger.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <pthread.h>

static FILE *log_fp = NULL;
static LogLevel min_level = LOG_INFO;
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

static const char *level_strings[] = {
    "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};

int logger_init(const char *logfile, LogLevel level) {
    min_level = level;
    if (logfile) {
        log_fp = fopen(logfile, "a");
        if (!log_fp) {
            fprintf(stderr, "Cannot open log file: %s\n", logfile);
            return -1;
        }
    }
    return 0;
}

void logger_close(void) {
    if (log_fp && log_fp != stderr) {
        fclose(log_fp);
    }
}

void logger_log(LogLevel level, const char *file, int line, const char *fmt, ...) {
    if (level < min_level) return;

    pthread_mutex_lock(&log_mutex);

    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    char timebuf[64];
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", t);

    FILE *out = log_fp ? log_fp : stderr;

    fprintf(out, "[%s] [%s] %s:%d: ", 
            timebuf, level_strings[level], file, line);

    va_list args;
    va_start(args, fmt);
    vfprintf(out, fmt, args);
    va_end(args);

    fprintf(out, "\n");
    fflush(out);

    pthread_mutex_unlock(&log_mutex);
}

配置文件解析

; config.ini
[server]
port = 8080
threads = 4
max_connections = 1024

[log]
file = /var/log/http-server.log
level = info

[static]
root = ./static
// src/config.c — 简化的 INI 解析器
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

static char *trim(char *str) {
    while (isspace(*str)) str++;
    char *end = str + strlen(str) - 1;
    while (end > str && isspace(*end)) *end-- = '\0';
    return str;
}

int config_load(const char *filename, Config *cfg) {
    // 设置默认值
    cfg->port = 8080;
    cfg->threads = 4;
    cfg->max_connections = 1024;
    cfg->log_file = NULL;
    cfg->log_level = LOG_INFO;
    cfg->static_root = strdup("./static");

    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;

    char line[1024];
    char section[64] = "";

    while (fgets(line, sizeof(line), fp)) {
        char *trimmed = trim(line);
        if (trimmed[0] == '\0' || trimmed[0] == ';' || trimmed[0] == '#') {
            continue;  // 空行或注释
        }

        if (trimmed[0] == '[') {
            // 节
            char *end = strchr(trimmed, ']');
            if (end) {
                *end = '\0';
                strncpy(section, trimmed + 1, sizeof(section) - 1);
            }
            continue;
        }

        // 键值对
        char *eq = strchr(trimmed, '=');
        if (!eq) continue;

        *eq = '\0';
        char *key = trim(trimmed);
        char *value = trim(eq + 1);

        if (strcmp(section, "server") == 0) {
            if (strcmp(key, "port") == 0) cfg->port = atoi(value);
            else if (strcmp(key, "threads") == 0) cfg->threads = atoi(value);
            else if (strcmp(key, "max_connections") == 0) cfg->max_connections = atoi(value);
        } else if (strcmp(section, "log") == 0) {
            if (strcmp(key, "file") == 0) cfg->log_file = strdup(value);
            else if (strcmp(key, "level") == 0) {
                if (strcmp(value, "debug") == 0) cfg->log_level = LOG_DEBUG;
                else if (strcmp(value, "info") == 0) cfg->log_level = LOG_INFO;
                else if (strcmp(value, "warn") == 0) cfg->log_level = LOG_WARN;
                else if (strcmp(value, "error") == 0) cfg->log_level = LOG_ERROR;
            }
        } else if (strcmp(section, "static") == 0) {
            if (strcmp(key, "root") == 0) {
                free(cfg->static_root);
                cfg->static_root = strdup(value);
            }
        }
    }

    fclose(fp);
    return 0;
}

信号处理

// 优雅关闭(Graceful Shutdown)
#include <signal.h>

static Server *g_server = NULL;

static void signal_handler(int sig) {
    (void)sig;
    printf("\nReceived signal %d, shutting down...\n", sig);
    if (g_server) {
        server_stop(g_server);
    }
}

static void setup_signals(void) {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);   // Ctrl+C
    sigaction(SIGTERM, &sa, NULL);  // kill
    sigaction(SIGHUP, &sa, NULL);   // 终端关闭
}

main.c 入口

// src/main.c
#include "server.h"
#include "router.h"
#include "logger.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static Server g_server;

static void signal_handler(int sig) {
    (void)sig;
    printf("\nShutting down...\n");
    server_stop(&g_server);
}

int main(int argc, char *argv[]) {
    const char *config_file = "config.ini";
    if (argc > 1) config_file = argv[1];

    // 加载配置
    Config cfg;
    if (config_load(config_file, &cfg) < 0) {
        fprintf(stderr, "Failed to load config: %s\n", config_file);
        return 1;
    }

    // 初始化日志
    if (logger_init(cfg.log_file, cfg.log_level) < 0) {
        return 1;
    }

    LOG_INFO("Starting HTTP server on port %d", cfg.port);

    // 初始化路由
    router_init();

    // 设置信号处理
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    // 初始化并启动服务器
    if (server_init(&g_server, cfg.port, cfg.threads) < 0) {
        LOG_FATAL("Failed to initialize server");
        return 1;
    }

    server_start(&g_server);
    server_destroy(&g_server);

    logger_close();
    LOG_INFO("Server stopped");

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(HttpServer VERSION 1.0.0 LANGUAGES C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 源文件
set(SOURCES
    src/main.c
    src/server.c
    src/http.c
    src/router.c
    src/logger.c
    src/config.c
)

# 可执行文件
add_executable(http-server ${SOURCES})
target_include_directories(http-server PRIVATE include)
target_compile_options(http-server PRIVATE -Wall -Wextra -Wpedantic)
target_link_libraries(http-server PRIVATE pthread)

# Debug 配置
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_compile_definitions(http-server PRIVATE DEBUG)
    target_compile_options(http-server PRIVATE -g -O0)
endif()

# Release 配置
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    target_compile_definitions(http-server PRIVATE NDEBUG)
    target_compile_options(http-server PRIVATE -O2)
endif()

# 安装
install(TARGETS http-server RUNTIME DESTINATION bin)
install(FILES config.ini DESTINATION etc/http-server)
install(DIRECTORY static/ DESTINATION share/http-server/static)

# 测试
enable_testing()
add_executable(test_http test/test_http.c src/http.c)
target_include_directories(test_http PRIVATE include)
target_compile_options(test_http PRIVATE -Wall -g)
add_test(NAME test_http COMMAND test_http)

压力测试

使用 wrk 测试

# 安装 wrk
sudo apt install wrk

# 基本测试
wrk -t4 -c100 -d10s http://localhost:8080/

# 带脚本的 POST 测试
cat > post.lua << 'EOF'
wrk.method = "POST"
wrk.body   = '{"key": "value"}'
wrk.headers["Content-Type"] = "application/json"
EOF

wrk -t4 -c100 -d10s -s post.lua http://localhost:8080/api/echo

使用 ab 测试

# 安装 ab
sudo apt install apache2-utils

# 测试
ab -n 10000 -c 100 http://localhost:8080/

# 输出示例:
# Concurrency Level:      100
# Time taken for tests:   2.345 seconds
# Complete requests:      10000
# Failed requests:        0
# Requests per second:    4264.83 [#/sec] (mean)
# Time per request:       23.448 [ms] (mean)

性能优化

线程池

// include/pool.h
#ifndef POOL_H
#define POOL_H

#include <pthread.h>

typedef void (*TaskFunc)(void *arg);

typedef struct {
    TaskFunc func;
    void *arg;
} Task;

typedef struct {
    pthread_t *threads;
    Task *queue;
    int queue_size;
    int queue_capacity;
    int head;
    int tail;
    int count;
    int thread_count;
    pthread_mutex_t mutex;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
    bool shutdown;
} ThreadPool;

int pool_init(ThreadPool *pool, int threads);
int pool_submit(ThreadPool *pool, TaskFunc func, void *arg);
void pool_destroy(ThreadPool *pool);

#endif

性能优化清单

优化项说明预期效果
SO_REUSEPORT多进程/线程监听同一端口+30% 连接率
TCP_NODELAY禁用 Nagle 算法-延迟
非阻塞 I/OO_NONBLOCK + epoll高并发
线程池避免频繁创建线程-CPU 开销
内存池避免频繁 malloc/free-内存碎片
sendfile零拷贝文件发送+静态文件性能
HTTP keep-alive连接复用-连接开销
编译优化-O2 -flto -march=native+10% 性能

sendfile 零拷贝

#include <sys/sendfile.h>

static void send_file(int client_fd, int file_fd, off_t offset, size_t size) {
    while (size > 0) {
        ssize_t sent = sendfile(client_fd, file_fd, &offset, size);
        if (sent <= 0) break;
        size -= sent;
    }
}

内存池

typedef struct {
    char *buffer;
    size_t capacity;
    size_t used;
} MemoryPool;

static MemoryPool *pool_create(size_t capacity) {
    MemoryPool *pool = malloc(sizeof(MemoryPool));
    pool->buffer = malloc(capacity);
    pool->capacity = capacity;
    pool->used = 0;
    return pool;
}

static void *pool_alloc(MemoryPool *pool, size_t size) {
    if (pool->used + size > pool->capacity) return NULL;
    void *ptr = pool->buffer + pool->used;
    pool->used += size;
    return ptr;
}

static void pool_reset(MemoryPool *pool) {
    pool->used = 0;
}

Docker 部署

# Dockerfile
FROM ubuntu:22.04 AS builder

RUN apt-get update && apt-get install -y \
    build-essential cmake && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY CMakeLists.txt .
COPY include/ include/
COPY src/ src/
COPY test/ test/

RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && \
    cmake --build build --parallel

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    libpthread-stubs0-dev && \
    rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/build/http-server /usr/local/bin/
COPY config.ini /etc/http-server/config.ini
COPY static/ /usr/local/share/http-server/static/

EXPOSE 8080

CMD ["http-server", "/etc/http-server/config.ini"]
# 构建镜像
docker build -t http-server .

# 运行
docker run -p 8080:8080 -v $(pwd)/static:/usr/local/share/http-server/static http-server

# 压力测试
wrk -t4 -c100 -d10s http://localhost:8080/

⚠️ 注意点

  1. SIGPIPE:必须忽略或处理,否则写入关闭的连接会崩溃
  2. 目录遍历:静态文件服务必须检查 .. 防止安全漏洞
  3. 缓冲区溢出:HTTP 头部解析要严格检查长度
  4. 内存泄漏:每个请求分配的内存必须正确释放
  5. 连接超时:生产环境需要实现空闲连接超时回收
  6. 日志锁竞争:高并发下日志的 mutex 可能成为瓶颈

💡 提示

  1. 先用单线程调试:确保功能正确后再加线程
  2. Valgrind 检查valgrind --leak-check=full ./http-server
  3. curl 测试curl -v http://localhost:8080/api/hello
  4. strace 排查strace -f -e trace=network ./http-server
  5. tcpdump 抓包tcpdump -i lo port 8080 -A
  6. 编译警告全开-Wall -Wextra -Werror 在开发阶段使用

扩展阅读