文件结构
Bash
web_server/
├── Makefile # 交叉编译配置
├── web_server.c # 主程序入口
├── server.h # 头文件
├── process.c # 多进程实现
├── thread.c # 多线程实现
├── epoll.c # IO多路复用实现
├── handler.c # 请求处理
└── static/ # Web文件目录
├── index.html
├── style.css
└── logo.png
1. 头文件 (server.h)
C
#ifndef SERVER_H
#define SERVER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <pthread.h>
#include <dirent.h>
// 服务器配置
#define PORT 8080 // 监听端口
#define BACKLOG 10 // 最大等待连接数
#define BUFFER_SIZE 4096 // 缓冲区大小
#define STATIC_DIR "./static" // Web文件目录
// 函数声明
void start_multi_process(int server_fd);
void start_multi_thread(int server_fd);
void start_epoll(int server_fd);
void handle_request(int client_fd);
void send_error(int client_fd, int code);
const char *get_content_type(const char *path);
#endif
2. 主程序 (web_server.c)
C
#include "server.h"
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s [process|thread|epoll]\n", argv[0]);
exit(EXIT_FAILURE);
}
// 创建TCP套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(PORT)
};
// 设置端口复用(避免"Address already in use"错误)
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 绑定端口
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);
}
printf("Server running on port %d\n", PORT);
printf("Web files directory: %s\n", STATIC_DIR);
// 根据参数选择运行模式
if (strcmp(argv[1], "process") == 0) {
start_multi_process(server_fd);
} else if (strcmp(argv[1], "thread") == 0) {
start_multi_thread(server_fd);
} else if (strcmp(argv[1], "epoll") == 0) {
start_epoll(server_fd);
} else {
fprintf(stderr, "Invalid mode. Use process, thread or epoll\n");
}
close(server_fd);
return 0;
}
3. 多进程实现 (process.c)
C
#include "server.h"
void start_multi_process(int server_fd) {
printf("Running in multi-process mode\n");
while (1) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
// 接受客户端连接
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
// 创建子进程处理请求
pid_t pid = fork();
if (pid == 0) { // 子进程
close(server_fd); // 子进程不需要监听套接字
handle_request(client_fd);
close(client_fd);
exit(EXIT_SUCCESS); // 处理完成后退出
} else if (pid > 0) { // 父进程
close(client_fd); // 父进程不需要客户端套接字
waitpid(-1, NULL, WNOHANG); // 非阻塞回收僵尸进程
} else {
perror("fork failed");
close(client_fd);
}
}
}
4. 多线程实现 (thread.c)
C
#include "server.h"
// 线程处理函数
void* thread_handler(void* arg) {
int client_fd = *(int*)arg;
free(arg); // 释放动态分配的内存
handle_request(client_fd);
close(client_fd);
return NULL;
}
void start_multi_thread(int server_fd) {
printf("Running in multi-thread mode\n");
while (1) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
// 接受客户端连接
int *client_fd = malloc(sizeof(int));
if (!client_fd) {
perror("malloc failed");
continue;
}
*client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
if (*client_fd < 0) {
free(client_fd);
perror("accept failed");
continue;
}
// 创建线程处理请求
pthread_t tid;
if (pthread_create(&tid, NULL, thread_handler, client_fd) != 0) {
perror("pthread_create failed");
close(*client_fd);
free(client_fd);
} else {
pthread_detach(tid); // 分离线程,自动回收资源
}
}
}
5. IO多路复用实现 (epoll.c)
C
#include "server.h"
#define MAX_EVENTS 1024
void start_epoll(int server_fd) {
printf("Running in epoll mode\n");
// 创建epoll实例
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1 failed");
return;
}
// 添加服务器套接字到epoll
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) {
perror("epoll_ctl failed");
close(epoll_fd);
return;
}
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 len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
// 设置非阻塞模式
fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | 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 {
// 客户端请求
handle_request(events[i].data.fd);
// 移除并关闭客户端
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
}
}
}
close(epoll_fd);
}
6. 请求处理 (handler.c)
C
#include "server.h"
void handle_request(int client_fd) {
char buffer[BUFFER_SIZE];
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
if (bytes_read <= 0) {
perror("read failed or connection closed");
return;
}
buffer[bytes_read] = '\0';
// 解析HTTP请求方法 (GET/POST等)
char *method = strtok(buffer, " ");
if (!method) {
send_error(client_fd, 400);
return;
}
// 只处理GET请求
if (strcmp(method, "GET") != 0) {
send_error(client_fd, 501); // 501 Not Implemented
return;
}
// 获取请求路径
char *path = strtok(NULL, " ");
if (!path) {
send_error(client_fd, 400);
return;
}
// 安全过滤:防止路径遍历攻击
if (strstr(path, "..") || strstr(path, "//")) {
send_error(client_fd, 403); // 403 Forbidden
return;
}
// 处理默认路径
if (strcmp(path, "/") == 0) {
path = "/index.html";
}
// 构建完整文件路径
char full_path[256];
snprintf(full_path, sizeof(full_path), "%s%s", STATIC_DIR, path);
// 检查文件是否存在
struct stat st;
if (stat(full_path, &st) < 0) {
send_error(client_fd, 404);
return;
}
// 检查是否是目录
if (S_ISDIR(st.st_mode)) {
// 如果是目录,尝试查找index.html
char index_path[256];
snprintf(index_path, sizeof(index_path), "%s/index.html", full_path);
if (stat(index_path, &st) < 0 || S_ISDIR(st.st_mode)) {
send_error(client_fd, 403); // 目录浏览禁止
return;
}
strcpy(full_path, index_path);
}
// 打开文件
int fd = open(full_path, O_RDONLY);
if (fd < 0) {
perror("open file failed");
send_error(client_fd, 500);
return;
}
// 获取内容类型
const char *content_type = get_content_type(full_path);
// 构建HTTP响应头
char header[512];
int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Server: SimpleWebServer\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n" // 兼容IE8需要明确长度
"Connection: close\r\n\r\n", // 兼容IE8需要显式关闭
content_type, st.st_size);
// 发送响应头
if (send(client_fd, header, header_len, 0) < 0) {
perror("send header failed");
close(fd);
return;
}
// 发送文件内容
ssize_t bytes, total_sent = 0;
char file_buffer[BUFFER_SIZE];
while ((bytes = read(fd, file_buffer, BUFFER_SIZE)) > 0) {
ssize_t sent = send(client_fd, file_buffer, bytes, 0);
if (sent < 0) {
perror("send file content failed");
break;
}
total_sent += sent;
}
close(fd);
printf("Served: %s (%ld bytes)\n", path, total_sent);
}
// 根据文件扩展名获取内容类型
const char *get_content_type(const char *path) {
const char *ext = strrchr(path, '.');
if (!ext) return "text/plain";
// 常见MIME类型映射
if (strcmp(ext, ".html") == 0) return "text/html; charset=utf-8";
if (strcmp(ext, ".htm") == 0) return "text/html; charset=utf-8";
if (strcmp(ext, ".css") == 0) return "text/css";
if (strcmp(ext, ".js") == 0) return "application/javascript";
if (strcmp(ext, ".json") == 0) return "application/json";
if (strcmp(ext, ".png") == 0) return "image/png";
if (strcmp(ext, ".jpg") == 0) return "image/jpeg";
if (strcmp(ext, ".jpeg") == 0) return "image/jpeg";
if (strcmp(ext, ".gif") == 0) return "image/gif";
if (strcmp(ext, ".ico") == 0) return "image/x-icon";
if (strcmp(ext, ".svg") == 0) return "image/svg+xml";
if (strcmp(ext, ".txt") == 0) return "text/plain; charset=utf-8";
return "application/octet-stream"; // 默认二进制流
}
// 发送错误响应
void send_error(int client_fd, int code) {
const char *messages[] = {
[400] = "Bad Request",
[403] = "Forbidden",
[404] = "Not Found",
[500] = "Internal Server Error",
[501] = "Not Implemented"
};
const char *message = messages[code] ? messages[code] : "Unknown Error";
char response[512];
int len = snprintf(response, sizeof(response),
"HTTP/1.1 %d %s\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Connection: close\r\n\r\n"
"<html><body><h1>%d %s</h1></body></html>",
code, message, code, message);
send(client_fd, response, len, 0);
printf("Error %d: %s\n", code, message);
}
7. Makefile 交叉编译配置
makefile
# 交叉编译工具链配置
# 默认使用本地GCC,设置CROSS_COMPILE变量可切换交叉编译
CROSS_COMPILE ?=
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -Wextra -O2 -I.
LDFLAGS = -lpthread
# 目标文件列表
SRCS = web_server.c process.c thread.c epoll.c handler.c
OBJS = $(SRCS:.c=.o)
TARGET = web_server
# 默认构建目标
all: $(TARGET)
# 链接可执行文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# 编译C源文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 创建静态文件目录
static:
mkdir -p static
@echo "<html><body><h1>Hello from Web Server!</h1></body></html>" > static/index.html
@echo "/* Sample CSS */ body { background: #f0f0f0; }" > static/style.css
@echo "Static files created in static/ directory"
# 交叉编译示例 (ARM架构)
cross-arm:
$(MAKE) CROSS_COMPILE=arm-linux-gnueabihf-
# 清理构建文件
clean:
rm -f $(OBJS) $(TARGET)
# 安装到系统目录 (需要root权限)
install: $(TARGET)
cp $(TARGET) /usr/local/bin/
mkdir -p /var/www/static
cp -r static/* /var/www/static/
.PHONY: all clean install static cross-arm
使用说明
编译服务器:
Bash
# 本地编译
make
# ARM交叉编译
make cross-arm
# 创建静态文件目录
make static
运行服务器:
Bash
# 多进程模式
./web_server process
# 多线程模式
./web_server thread
# epoll模式
./web_server epoll对这个程序启动GDB调试
最新发布