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/O | O_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/
⚠️ 注意点
- SIGPIPE:必须忽略或处理,否则写入关闭的连接会崩溃
- 目录遍历:静态文件服务必须检查
..防止安全漏洞 - 缓冲区溢出:HTTP 头部解析要严格检查长度
- 内存泄漏:每个请求分配的内存必须正确释放
- 连接超时:生产环境需要实现空闲连接超时回收
- 日志锁竞争:高并发下日志的 mutex 可能成为瓶颈
💡 提示
- 先用单线程调试:确保功能正确后再加线程
- Valgrind 检查:
valgrind --leak-check=full ./http-server - curl 测试:
curl -v http://localhost:8080/api/hello - strace 排查:
strace -f -e trace=network ./http-server - tcpdump 抓包:
tcpdump -i lo port 8080 -A - 编译警告全开:
-Wall -Wextra -Werror在开发阶段使用