<think>我们正在实现一个支持GET和POST请求的多线程Web服务器,使用C语言编写,并添加详细注释。我们将使用线程池和epoll来实现高并发处理。以下是一个整合的代码,包含详细注释:
设计要点:
1. 使用线程池处理连接,避免频繁创建销毁线程。
2. 使用epoll进行I/O多路复用,监听多个套接字。
3. 解析HTTP请求,支持GET和POST方法。
4. 实现简单的路由,处理不同的请求路径。
5. 发送HTTP响应,包括状态码、头部和内容。
注意:为了简化,我们不考虑持久连接(Keep-Alive),每次请求后关闭连接。
我们将代码写在一个文件中,并添加详细的注释。</think>以下是一个详细注释的C语言多线程Web服务器实现,支持GET/POST请求处理和高并发性能。代码整合在单个C文件中:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#define PORT 8080 // 服务器监听端口
#define THREAD_POOL_SIZE 16 // 线程池大小
#define MAX_EVENTS 1024 // epoll最大事件数
#define BUFFER_SIZE 8192 // 读写缓冲区大小
#define BACKLOG 128 // 最大等待连接数
#define MAX_PATH_LEN 256 // 最大路径长度
// HTTP请求结构体
typedef struct {
char method[16]; // 请求方法(GET/POST)
char path[MAX_PATH_LEN]; // 请求路径
char protocol[16]; // 协议版本
char headers[BUFFER_SIZE]; // 请求头
char *body; // 请求体(POST使用)
int content_length; // 内容长度
} HttpRequest;
// HTTP响应结构体
typedef struct {
int status_code; // 状态码
char *content; // 响应内容
size_t content_len; // 内容长度
char headers[BUFFER_SIZE]; // 响应头
} HttpResponse;
// 任务队列节点
typedef struct TaskNode {
int client_fd; // 客户端文件描述符
struct TaskNode *next; // 下一个任务指针
} TaskNode;
// 线程安全任务队列
typedef struct {
TaskNode *front; // 队列头部
TaskNode *rear; // 队列尾部
pthread_mutex_t lock; // 互斥锁
pthread_cond_t cond; // 条件变量
int shutdown; // 关闭标志
} TaskQueue;
// 线程池结构
typedef struct {
pthread_t threads[THREAD_POOL_SIZE]; // 工作线程数组
TaskQueue *queue; // 任务队列
int shutdown; // 关闭标志
} ThreadPool;
// 初始化任务队列
TaskQueue* init_task_queue() {
TaskQueue *q = malloc(sizeof(TaskQueue));
if (!q) {
perror("Failed to allocate task queue");
exit(EXIT_FAILURE);
}
q->front = q->rear = NULL;
q->shutdown = 0;
// 初始化互斥锁和条件变量
if (pthread_mutex_init(&q->lock, NULL) != 0) {
perror("Mutex init failed");
exit(EXIT_FAILURE);
}
if (pthread_cond_init(&q->cond, NULL) != 0) {
perror("Condition variable init failed");
exit(EXIT_FAILURE);
}
return q;
}
// 添加任务到队列
void enqueue(TaskQueue *q, int client_fd) {
// 创建新任务节点
TaskNode *node = malloc(sizeof(TaskNode));
if (!node) {
perror("Failed to allocate task node");
return;
}
node->client_fd = client_fd;
node->next = NULL;
// 加锁操作队列
pthread_mutex_lock(&q->lock);
// 队列为空时直接添加
if (q->rear == NULL) {
q->front = q->rear = node;
}
// 队列非空时添加到尾部
else {
q->rear->next = node;
q->rear = node;
}
// 唤醒等待线程并解锁
pthread_cond_signal(&q->cond);
pthread_mutex_unlock(&q->lock);
}
// 从队列获取任务
int dequeue(TaskQueue *q) {
pthread_mutex_lock(&q->lock);
// 队列为空时等待条件变量
while (q->front == NULL && !q->shutdown) {
pthread_cond_wait(&q->cond, &q->lock);
}
// 服务器关闭时返回-1
if (q->shutdown) {
pthread_mutex_unlock(&q->lock);
return -1;
}
// 获取队列头部任务
TaskNode *node = q->front;
int client_fd = node->client_fd;
// 更新队列头部
if (q->front == q->rear) {
q->front = q->rear = NULL;
} else {
q->front = q->front->next;
}
// 释放节点内存并解锁
free(node);
pthread_mutex_unlock(&q->lock);
return client_fd;
}
// 工作线程函数
void* worker_thread(void *arg) {
ThreadPool *pool = (ThreadPool*)arg;
// 持续处理任务直到关闭
while (!pool->shutdown) {
// 从队列获取客户端连接
int client_fd = dequeue(pool->queue);
if (client_fd < 0) continue;
// 处理客户端请求
handle_client(client_fd);
// 关闭客户端连接
close(client_fd);
}
return NULL;
}
// 初始化线程池
ThreadPool* init_thread_pool(TaskQueue *queue) {
ThreadPool *pool = malloc(sizeof(ThreadPool));
if (!pool) {
perror("Failed to allocate thread pool");
exit(EXIT_FAILURE);
}
pool->queue = queue;
pool->shutdown = 0;
// 创建工作线程
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
if (pthread_create(&pool->threads[i], NULL, worker_thread, pool) != 0) {
perror("Failed to create thread");
exit(EXIT_FAILURE);
}
}
return pool;
}
// 创建服务器套接字
int create_server_socket(int port) {
int server_fd;
struct sockaddr_in addr;
// 创建TCP套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置SO_REUSEADDR选项避免TIME_WAIT状态
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) {
perror("Setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 配置服务器地址
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
// 绑定地址到套接字
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 开始监听连接
if (listen(server_fd, BACKLOG) < 0) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
return server_fd;
}
// 解析HTTP请求
HttpRequest* parse_request(char *buffer) {
HttpRequest *req = malloc(sizeof(HttpRequest));
if (!req) {
perror("Failed to allocate request");
return NULL;
}
memset(req, 0, sizeof(HttpRequest));
// 解析请求行: METHOD PATH PROTOCOL
if (sscanf(buffer, "%15s %255s %15s", req->method, req->path, req->protocol) != 3) {
free(req);
return NULL;
}
// 查找头部结束位置(空行)
char *header_end = strstr(buffer, "\r\n\r\n");
if (header_end) {
// 计算头部长度并复制
size_t header_len = header_end - buffer;
strncpy(req->headers, buffer, header_len);
req->headers[header_len] = '\0';
// 处理POST请求体
if (strcmp(req->method, "POST") == 0) {
// 查找Content-Length头部
char *cl_header = strstr(req->headers, "Content-Length:");
if (cl_header) {
// 获取内容长度
if (sscanf(cl_header, "Content-Length: %d", &req->content_length) != 1) {
req->content_length = 0;
}
// 分配内存并复制请求体
if (req->content_length > 0) {
req->body = malloc(req->content_length + 1);
if (!req->body) {
perror("Failed to allocate request body");
free(req);
return NULL;
}
memcpy(req->body, header_end + 4, req->content_length);
req->body[req->content_length] = '\0';
}
}
}
}
return req;
}
// 发送HTTP响应
void send_response(int client_fd, HttpResponse *res) {
char status_line[128];
// 根据状态码生成状态行
switch (res->status_code) {
case 200:
sprintf(status_line, "HTTP/1.1 200 OK\r\n");
break;
case 404:
sprintf(status_line, "HTTP/1.1 404 Not Found\r\n");
break;
case 500:
sprintf(status_line, "HTTP/1.1 500 Internal Server Error\r\n");
break;
default:
sprintf(status_line, "HTTP/1.1 %d Status\r\n", res->status_code);
}
// 构建完整响应
char full_response[BUFFER_SIZE * 2];
int header_len = snprintf(full_response, sizeof(full_response),
"%s%sContent-Length: %zu\r\n\r\n",
status_line, res->headers, res->content_len);
// 复制响应内容
if (res->content && res->content_len > 0) {
memcpy(full_response + header_len, res->content, res->content_len);
}
// 计算总长度并发送
size_t total_len = header_len + res->content_len;
send(client_fd, full_response, total_len, 0);
}
// 安全路径检查(防止路径遍历攻击)
int is_safe_path(const char *path) {
// 检查是否包含父目录引用
if (strstr(path, "..") != NULL) {
return 0;
}
// 检查是否尝试访问根目录之外
if (strstr(path, "/etc/passwd") != NULL) {
return 0;
}
return 1;
}
// 处理GET请求
void handle_get(int client_fd, HttpRequest *req, HttpResponse *res) {
// 默认响应
res->status_code = 200;
res->content = "Hello, World!";
res->content_len = strlen(res->content);
// 简单路由处理
if (strcmp(req->path, "/") == 0) {
// 首页响应
res->content = "<html><body><h1>Welcome to C Web Server</h1></body></html>";
res->content_len = strlen(res->content);
snprintf(res->headers, sizeof(res->headers), "Content-Type: text/html\r\n");
}
// 处理文件请求
else {
char file_path[MAX_PATH_LEN];
snprintf(file_path, sizeof(file_path), ".%s", req->path);
// 检查路径安全性
if (!is_safe_path(file_path)) {
res->status_code = 403;
res->content = "Forbidden";
res->content_len = strlen(res->content);
return;
}
// 检查文件是否存在
struct stat st;
if (stat(file_path, &st) == 0) {
// 打开文件
int fd = open(file_path, O_RDONLY);
if (fd >= 0) {
// 设置响应头
const char *ext = strrchr(file_path, '.');
if (ext) {
if (strcmp(ext, ".html") == 0) {
snprintf(res->headers, sizeof(res->headers),
"Content-Type: text/html\r\n");
} else if (strcmp(ext, ".css") == 0) {
snprintf(res->headers, sizeof(res->headers),
"Content-Type: text/css\r\n");
} else if (strcmp(ext, ".js") == 0) {
snprintf(res->headers, sizeof(res->headers),
"Content-Type: application/javascript\r\n");
} else {
snprintf(res->headers, sizeof(res->headers),
"Content-Type: application/octet-stream\r\n");
}
}
// 使用sendfile零拷贝传输文件
sendfile(client_fd, fd, NULL, st.st_size);
close(fd);
return;
}
}
// 文件不存在
res->status_code = 404;
res->content = "404 Not Found";
res->content_len = strlen(res->content);
}
}
// 处理POST请求
void handle_post(HttpRequest *req, HttpResponse *res) {
// 默认响应
res->status_code = 200;
res->content = "Form submitted successfully";
res->content_len = strlen(res->content);
// 示例:处理表单提交
if (strstr(req->path, "/submit")) {
printf("Received POST data: %.*s\n", req->content_length, req->body);
snprintf(res->headers, sizeof(res->headers),
"Content-Type: text/plain\r\n");
}
// 处理文件上传
else if (strstr(req->path, "/upload")) {
// 查找boundary
char *boundary = strstr(req->headers, "boundary=");
if (boundary) {
boundary += 9; // 移动到boundary值
// 截断boundary字符串
char *end = strchr(boundary, '\r');
if (end) *end = '\0';
// 解析multipart数据
char *body_ptr = req->body;
while ((body_ptr = strstr(body_ptr, boundary))) {
// 跳过头部信息
body_ptr = strstr(body_ptr, "\r\n\r\n");
if (!body_ptr) break;
body_ptr += 4; // 跳过空行
// 查找下一个boundary
char *next_boundary = strstr(body_ptr, boundary);
if (!next_boundary) break;
// 计算文件内容长度
size_t file_size = next_boundary - body_ptr - 4; // 减去前置的\r\n
// 写入文件
FILE *file = fopen("uploaded_file", "wb");
if (file) {
fwrite(body_ptr, 1, file_size, file);
fclose(file);
res->content = "File uploaded successfully";
res->content_len = strlen(res->content);
return;
}
}
}
// 文件上传失败
res->status_code = 500;
res->content = "File upload failed";
res->content_len = strlen(res->content);
}
}
// 处理客户端请求
void handle_client(int client_fd) {
char buffer[BUFFER_SIZE];
// 读取客户端请求
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
// 错误或连接关闭
if (bytes_read <= 0) {
close(client_fd);
return;
}
// 确保字符串以NULL结尾
buffer[bytes_read] = '\0';
// 解析HTTP请求
HttpRequest *req = parse_request(buffer);
if (!req) {
close(client_fd);
return;
}
// 初始化响应结构
HttpResponse res = {0};
// 根据请求方法路由处理
if (strcmp(req->method, "GET") == 0) {
handle_get(client_fd, req, &res);
} else if (strcmp(req->method, "POST") == 0) {
handle_post(req, &res);
} else {
// 不支持的HTTP方法
res.content = "Method Not Allowed";
res.content_len = strlen(res.content);
res.status_code = 405;
}
// 发送响应(文件请求已直接发送,跳过)
if (res.content) {
send_response(client_fd, &res);
}
// 清理资源
if (req->body) free(req->body);
free(req);
}
int main() {
// 创建服务器套接字
int server_fd = create_server_socket(PORT);
printf("Server listening on port %d\n", PORT);
// 创建任务队列
TaskQueue *queue = init_task_queue();
// 创建线程池
ThreadPool *pool = init_thread_pool(queue);
// 创建epoll实例
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1 failed");
exit(EXIT_FAILURE);
}
// 添加服务器套接字到epoll
struct epoll_event ev;
ev.events = EPOLLIN; // 监听可读事件
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) {
perror("epoll_ctl failed");
exit(EXIT_FAILURE);
}
// 主事件循环
struct epoll_event events[MAX_EVENTS];
while (1) {
// 等待事件(无限等待)
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait failed");
continue;
}
// 处理所有就绪事件
for (int i = 0; i < nfds; i++) {
// 新连接请求
if (events[i].data.fd == server_fd) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
// 接受新连接
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
// 设置非阻塞模式
int flags = fcntl(client_fd, F_GETFL, 0);
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
// 添加新连接到epoll(边缘触发模式)
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) {
perror("epoll_ctl add client failed");
close(client_fd);
}
}
// 现有连接有数据可读
else {
// 添加到任务队列
enqueue(queue, events[i].data.fd);
// 从epoll中移除,避免重复处理
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
}
}
}
// 清理资源(实际不会执行到这里)
close(server_fd);
close(epoll_fd);
return 0;
}
```
### 关键设计说明
1. **线程池模型**
- 预创建16个工作线程处理请求
- 使用互斥锁(`pthread_mutex_t`)和条件变量(`pthread_cond_t`)实现线程安全队列
- 避免线程频繁创建销毁的开销
2. **高并发实现**
```c
// 使用epoll实现I/O多路复用
int epoll_fd = epoll_create1(0);
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
// 边缘触发模式(EPOLLET)提高效率
ev.events = EPOLLIN | EPOLLET;
```
3. **零拷贝文件传输**
```c
// 使用sendfile系统调用高效传输文件
sendfile(client_fd, fd, NULL, st.st_size);
```
4. **安全防护**
```c
// 防止路径遍历攻击
int is_safe_path(const char *path) {
if (strstr(path, "..") != NULL) return 0;
if (strstr(path, "/etc/passwd") != NULL) return 0;
return 1;
}
```
5. **HTTP解析**
- 使用`sscanf`解析请求行
- 自动处理POST请求的内容长度
- 支持multipart/form-data文件上传
### 编译与测试
```bash
# 编译
gcc -o webserver server.c -lpthread
# 运行
./webserver
# 测试GET请求
curl http://localhost:8080/
curl http://localhost:8080/index.html
# 测试POST请求
curl -X POST -d "name=value" http://localhost:8080/submit
# 测试文件上传
curl -F "file=@test.txt" http://localhost:8080/upload
```
### 性能优化建议
1. **连接复用**:实现HTTP Keep-Alive减少连接开销
2. **内存池**:预分配请求/响应内存减少`malloc`调用
3. **日志系统**:添加异步日志记录功能
4. **配置文件**:支持端口、线程数等参数配置
5. **SSL支持**:添加OpenSSL实现HTTPS