/* webserver.c - 支持多线程/多进程/IO复用的Web服务器 */
#include <stdio.h>
#include <stdlib.h> /* 内存分配、系统命令 */
#include <string.h>
#include <unistd.h> /* POSIX操作系统API */
#include <sys/socket.h> /* 套接字编程接口 */
#include <netinet/in.h> /* 互联网地址族 */
#include <arpa/inet.h> /* 互联网地址转换 */
#include <sys/stat.h> /* 文件状态信息 */
#include <fcntl.h> /* 文件控制选项 */
#include <pthread.h> /* POSIX线程 */
#include <sys/wait.h> /* 进程控制 */
#include <sys/select.h> /* IO多路复用 */
/* 服务器配置常量 */
#define PORT 80 /* 监听端口80,需要sudo运行 */
#define WEB_ROOT "./web"/* 网站根目录路径 */
#define BUFFER_SIZE 8192/* 读写缓冲区大小 */
/* 服务器运行模式 */
typedef enum
{
MODE_THREAD, /* 多线程模式 */
MODE_PROCESS, /* 多进程模式 */
MODE_MULTIPLEX /* IO多路复用模式 */
} ServerMode;
/* HTTP状态行常量 */
const char* http_200 = "HTTP/1.1 200 OK\r\n";
const char* http_404 = "HTTP/1.1 404 Not Found\r\n";
const char* http_500 = "HTTP/1.1 500 Internal Server Error\r\n";
/*
* 获取文件类型
* 参数: path - 文件路径
* 返回: 对应的MIME类型字符串
*/
const char* get_mime_type(const char *path)
{
/* 提取文件扩展名 */
const char *ext = strrchr(path, '.');
if (!ext)
{
return "text/plain"; /* 默认文本类型 */
}
/* 常见Web文件类型匹配 */
if (strcmp(ext, ".html") == 0)
{
return "text/html";
}
if (strcmp(ext, ".css") == 0)
{
return "text/css";
}
if (strcmp(ext, ".js") == 0)
{
return "application/javascript";
}
if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0)
{
return "image/jpeg";
}
if (strcmp(ext, ".png") == 0)
{
return "image/png";
}
return "application/octet-stream"; /* 二进制流默认类型 */
}
/*
* 发送文件内容到客户端
* 参数:
* client_sock - 客户端套接字
* file_path - 请求的文件路径
*/
void send_file(int client_sock, const char *file_path)
{
/* 尝试打开文件 */
FILE *file = fopen(file_path, "rb");
if (!file)
{
/* 文件不存在时发送404响应 */
send(client_sock, http_404, strlen(http_404), 0);
const char *msg = "Content-Type: text/html\r\n\r\n<h1>404 Not Found</h1>";
send(client_sock, msg, strlen(msg), 0);
return;
}
/* 获取文件大小 */
fseek(file, 0, SEEK_END);
long size = ftell(file); /* 获取指针当前位置,即文件字节数 */
fseek(file, 0, SEEK_SET); /* 重置文件指针 */
/* 构造HTTP响应头 */
char header[BUFFER_SIZE];
snprintf(header, sizeof(header),
"%sContent-Type: %s\r\nContent-Length: %ld\r\nConnection: close\r\n\r\n",
http_200, get_mime_type(file_path), size);
send(client_sock, header, strlen(header), 0);
/* 分块发送文件内容 */
char buffer[BUFFER_SIZE];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)
{
send(client_sock, buffer, bytes_read, 0);
}
fclose(file); /* 关闭文件描述符 */
}
/*
* 处理HTTP请求
* 参数: client_sock - 客户端连接套接字
*/
void handle_request(int client_sock)
{
char buffer[BUFFER_SIZE];
/* 接收客户端请求数据 */
ssize_t bytes_read = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (bytes_read <= 0)
{
return;
}
buffer[bytes_read] = '\0'; /* 确保字符串终止 */
/* 解析HTTP请求行 */
char method[16], path[256];
sscanf(buffer, "%s %s", method, path); /* 提取方法和路径 */
/* 调试输出 - 显示收到的请求 */
printf("Request: %s %s\n", method, path);
/* 安全处理:防御路径遍历攻击 */
char *pos;
while ((pos = strstr(path, "/../")) != NULL) /* 路径规范化,阻止通过/../回溯到上级目录 */
{
memmove(pos, pos + 3, strlen(pos + 3) + 1);
}
/* 构造实际文件路径 */
char full_path[512];
if (strcmp(path, "/") == 0)
{
/* 根请求默认返回Index.html */
snprintf(full_path, sizeof(full_path), "%s/Index.html", WEB_ROOT);
}
else
{
/* 拼接web根目录和请求路径 */
snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path);
}
/* 处理GET/POST方法 */
if (strcmp(method, "GET") == 0 || strcmp(method, "POST") == 0)
{
struct stat st;
/* 检查文件是否存在且是普通文件 */
if (stat(full_path, &st) == 0 && S_ISREG(st.st_mode)) {
send_file(client_sock, full_path);
} else {
/* 文件不存在时发送404 */
send(client_sock, http_404, strlen(http_404), 0);
const char *msg = "Content-Type: text/html\r\n\r\n<h1>404 Not Found</h1>";
send(client_sock, msg, strlen(msg), 0);
}
} else {
/* 不支持的HTTP方法 */
send(client_sock, http_500, strlen(http_500), 0);
const char *msg = "Content-Type: text/html\r\n\r\n<h1>Method Not Supported</h1>";
send(client_sock, msg, strlen(msg), 0);
}
close(client_sock); /* 关闭客户端连接 */
}
/*
* 线程处理函数(多线程模式使用)
* 参数: arg - 包含客户端套接字指针
*/
void* thread_handler(void *arg)
{
int client_sock = *((int*)arg); /* 解引用套接字 */
free(arg); /* 释放动态分配的内存 */
handle_request(client_sock); /* 处理请求 */
return NULL;
}
/*
* 多进程模式主循环
* 参数: server_sock - 服务器监听套接字
*/
void run_process_mode(int server_sock) {
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
/* 接受客户端连接 */
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock < 0) continue;
/* 创建子进程处理请求 */
pid_t pid = fork();
if (pid == 0)
{ /* 子进程 */
close(server_sock); /* 关闭不需要的监听套接字 */
handle_request(client_sock);
exit(0); /* 处理完成后退出 */
}
else
{ /* 父进程 */
close(client_sock); /* 父进程不需要客户端套接字 */
waitpid(-1, NULL, WNOHANG); /* 非阻塞回收僵尸进程 */
}
}
}
/*
* 多线程模式主循环
* 参数: server_sock - 服务器监听套接字
*/
void run_thread_mode(int server_sock) {
while (1)
{
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
/* 动态分配内存存储客户端套接字 */
int *client_sock = malloc(sizeof(int));
*client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &addr_len);
if (*client_sock < 0) continue;
/* 创建线程处理请求 */
pthread_t tid;
pthread_create(&tid, NULL, thread_handler, client_sock);
pthread_detach(tid); /* 分离线程自动回收资源 */
}
}
/*
* IO多路复用模式主循环
* 参数: server_sock - 服务器监听套接字
*/
void run_multiplex_mode(int server_sock) {
fd_set read_fds; /* 读文件描述符集合 */
int max_fd = server_sock; /* 当前最大文件描述符 */
while (1) {
FD_ZERO(&read_fds); /* 清空描述符集合 */
FD_SET(server_sock, &read_fds); /* 添加服务器套接字 */
/* 等待可读事件 */
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) break;
/* 检查服务器套接字是否有新连接 */
if (FD_ISSET(server_sock, &read_fds)) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock < 0) continue;
/* 直接处理请求(单线程) */
handle_request(client_sock);
}
}
}
/*
* 主函数 - 程序入口
* 参数:
* argc - 命令行参数个数
* argv - 命令行参数数组
*/
int main(int argc, char *argv[]) {
/* 检查命令行参数 */
if (argc < 2) {
fprintf(stderr, "Usage: %s <thread|process|multiplex>\n", argv[0]);
return 1;
}
/* 解析运行模式 */
ServerMode mode;
if (strcmp(argv[1], "thread") == 0) mode = MODE_THREAD;
else if (strcmp(argv[1], "process") == 0) mode = MODE_PROCESS;
else if (strcmp(argv[1], "multiplex") == 0) mode = MODE_MULTIPLEX;
else
{
fprintf(stderr, "Invalid mode. Use thread, process or multiplex\n");
return 1;
}
/* 创建TCP套接字 */
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock < 0)
{
perror("Socket creation failed");
return 1;
}
/* 配置服务器地址 */
struct sockaddr_in server_addr =
{
.sin_family = AF_INET, /* IPv4地址族 */
.sin_port = htons(PORT), /* 网络字节序端口 */
.sin_addr.s_addr = INADDR_ANY /* 监听所有接口 */
};
/* 设置套接字选项 - 地址重用 */
int opt = 1;
setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* 绑定套接字到地址 */
if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
return 1;
}
/* 开始监听,最大连接队列128 */
listen(server_sock, 128);
printf("Server running on port %d in %s mode\n", PORT, argv[1]);
/* 根据模式运行服务器 */
switch (mode)
{
case MODE_PROCESS:
run_process_mode(server_sock);
break;
case MODE_THREAD:
run_thread_mode(server_sock);
break;
case MODE_MULTIPLEX:
run_multiplex_mode(server_sock);
break;
}
close(server_sock); /* 关闭服务器套接字 */
return 0;
}
这是一份多模式的Web服务器实现代码,请在这份代码的基础上将三种模式升级为支持多个线程/进程同时并发处理的代码,并且添加进程/线程生成监控
最新发布