#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/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <pthread.h>
#define MAX_EVENTS 1024
#define BUFFER_SIZE 4096
#define SERVER_STRING "Server: jdbhttpd/0.5.0 (epoll)\r\n"
#define ISspace(x) isspace((int)(x))
#define CONNECTION_TIMEOUT 30 // 连接超时时间(秒)
// 连接状态结构体
typedef struct {
int fd; // 文件描述符
char buffer[BUFFER_SIZE]; // 读写缓冲区
size_t buffer_len; // 缓冲区数据长度
size_t buffer_sent; // 已发送字节数
int file_fd; // 打开的文件描述符
off_t file_size; // 文件大小
off_t file_sent; // 已发送文件字节数
char method[16]; // HTTP方法
char url[1024]; // 请求URL
char path[2048]; // 文件路径
int response_code; // 响应状态码
int content_length; // 内容长度
time_t last_activity; // 最后活动时间
int closed; // 连接是否已关闭
} connection_t;
// 全局连接列表
connection_t *connections[MAX_EVENTS] = {0};
pthread_mutex_t conn_mutex = PTHREAD_MUTEX_INITIALIZER;
// 函数声明
void set_nonblocking(int sock);
void handle_request(connection_t *conn);
void send_response(connection_t *conn, int epoll_fd);
void send_headers(connection_t *conn);
void close_connection(connection_t *conn, int epoll_fd);
void log_request(connection_t *conn);
const char *get_mime_type(const char *filename);
void url_decode(char *dest, const char *src);
void serve_directory(int client, const char *path);
void not_found(connection_t *conn);
void bad_request(connection_t *conn);
void unimplemented(connection_t *conn);
void forbidden(connection_t *conn);
int startup(unsigned short *port);
void check_timeouts(int epoll_fd);
void remove_connection(connection_t *conn);
// 设置非阻塞
void set_nonblocking(int sock) {
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
// 启动服务器
int startup(unsigned short *port) {
int httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1) {
perror("socket");
exit(1);
}
int opt = 1;
if (setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
close(httpd);
exit(1);
}
struct sockaddr_in name;
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) {
perror("bind");
close(httpd);
exit(1);
}
if (*port == 0) {
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) {
perror("getsockname");
close(httpd);
exit(1);
}
*port = ntohs(name.sin_port);
}
if (listen(httpd, 1024) < 0) {
perror("listen");
close(httpd);
exit(1);
}
return httpd;
}
// URL解码
void url_decode(char *dest, const char *src) {
char *p = dest;
while (*src) {
if (*src == '%') {
if (src[1] && src[2]) {
char hex[3] = {src[1], src[2], '\0'};
*p++ = (char)strtol(hex, NULL, 16);
src += 3;
} else {
*p++ = *src++;
}
} else if (*src == '+') {
*p++ = ' ';
src++;
} else {
*p++ = *src++;
}
}
*p = '\0';
}
// 获取MIME类型
const char *get_mime_type(const char *filename) {
const char *dot = strrchr(filename, '.');
if (!dot) return "text/plain";
if (strcasecmp(dot, ".html") == 0 || strcasecmp(dot, ".htm") == 0)
return "text/html";
if (strcasecmp(dot, ".css") == 0)
return "text/css";
if (strcasecmp(dot, ".js") == 0)
return "application/javascript";
if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcasecmp(dot, ".png") == 0)
return "image/png";
if (strcasecmp(dot, ".gif") == 0)
return "image/gif";
if (strcasecmp(dot, ".svg") == 0)
return "image/svg+xml";
if (strcasecmp(dot, ".json") == 0)
return "application/json";
if (strcasecmp(dot, ".ico") == 0)
return "image/x-icon";
if (strcasecmp(dot, ".woff") == 0)
return "font/woff";
if (strcasecmp(dot, ".woff2") == 0)
return "font/woff2";
if (strcasecmp(dot, ".ttf") == 0)
return "font/ttf";
return "application/octet-stream";
}
// 记录请求日志
void log_request(connection_t *conn) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
getpeername(conn->fd, (struct sockaddr*)&addr, &addr_len);
char *ip = inet_ntoa(addr.sin_addr);
printf("[%s] %s %s %s %d\n",
timestamp,
ip,
conn->method,
conn->url,
conn->response_code);
}
// 处理目录列表
void serve_directory(int client, const char *path) {
char buf[BUFFER_SIZE];
// 发送HTTP头
sprintf(buf, "HTTP/1.1 200 OK\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
// 发送HTML头部
sprintf(buf, "<html><head><title>Index of %s</title><style>"
"body { font-family: sans-serif; }"
"ul { list-style-type: none; padding: 0; }"
"li { padding: 5px; }"
"li:nth-child(odd) { background-color: #f0f0f0; }"
"</style></head>", path);
send(client, buf, strlen(buf), 0);
sprintf(buf, "<body><h1>Index of %s</h1><ul>", path);
send(client, buf, strlen(buf), 0);
// 打开目录
DIR *dir = opendir(path);
if (dir) {
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
// 跳过隐藏文件
if (ent->d_name[0] == '.') continue;
char full_path[2048];
sprintf(full_path, "%s/%s", path, ent->d_name);
struct stat st;
stat(full_path, &st);
char size_buf[32];
char type_icon[32];
if (S_ISDIR(st.st_mode)) {
strcpy(type_icon, "📁");
strcpy(size_buf, "[DIR]");
} else {
strcpy(type_icon, "📄");
if (st.st_size < 1024) {
sprintf(size_buf, "%ld B", st.st_size);
} else if (st.st_size < 1024 * 1024) {
sprintf(size_buf, "%.1f KB", st.st_size / 1024.0);
} else {
sprintf(size_buf, "%.1f MB", st.st_size / (1024.0 * 1024));
}
}
sprintf(buf, "<li>%s <a href=\"%s\">%s</a> <span style=\"float:right;\">%s</span></li>",
type_icon, ent->d_name, ent->d_name, size_buf);
send(client, buf, strlen(buf), 0);
}
closedir(dir);
}
// 发送HTML尾部
sprintf(buf, "</ul><p><em>Served by jdbhttpd/0.5.0</em></p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
// 处理请求
void handle_request(connection_t *conn) {
char *buf = conn->buffer;
size_t len = conn->buffer_len;
// 解析请求行
int i = 0, j = 0;
while (!ISspace(buf[j]) && (i < sizeof(conn->method) - 1) && (j < len)) {
conn->method[i] = buf[j];
i++; j++;
}
conn->method[i] = '\0';
// 支持GET、HEAD方法
if (strcasecmp(conn->method, "GET") && strcasecmp(conn->method, "POST") && strcasecmp(conn->method, "HEAD")) {
unimplemented(conn);
return;
}
// 读取URL
i = 0;
while (ISspace(buf[j]) && (j < len))
j++;
while (!ISspace(buf[j]) && (i < sizeof(conn->url) - 1) && (j < len)) {
conn->url[i] = buf[j];
i++; j++;
}
conn->url[i] = '\0';
// URL解码
char decoded_url[1024];
url_decode(decoded_url, conn->url);
// 构建文件路径 - 添加web前缀
snprintf(conn->path, sizeof(conn->path), "web%s", decoded_url);
// 防止路径遍历攻击
if (strstr(conn->path, "..")) {
forbidden(conn);
return;
}
// 处理目录请求: 如果路径以'/'结尾,则添加Index.html
size_t path_len = strlen(conn->path);
if (path_len > 0 && conn->path[path_len - 1] == '/') {
strncat(conn->path, "Index.html", sizeof(conn->path) - path_len - 1);
}
// 检查文件/目录是否存在
struct stat st;
if (stat(conn->path, &st) == -1) {
// 尝试添加.html扩展名(仅对无扩展名的路径)
if (!strrchr(conn->path, '.')) {
char alt_path[2048];
snprintf(alt_path, sizeof(alt_path), "%s.html", conn->path);
if (stat(alt_path, &st) == 0) {
strcpy(conn->path, alt_path);
} else {
not_found(conn);
return;
}
} else {
not_found(conn);
return;
}
}
// 如果是目录
if (S_ISDIR(st.st_mode)) {
// 检查目录中是否有index.html
char index_path[2048];
snprintf(index_path, sizeof(index_path), "%s/Index.html", conn->path);
if (stat(index_path, &st) == 0) {
strcpy(conn->path, index_path);
} else {
// 显示目录列表
serve_directory(conn->fd, conn->path);
conn->response_code = 200;
return;
}
}
// 打开文件
conn->file_fd = open(conn->path, O_RDONLY);
if (conn->file_fd == -1) {
if (errno == EACCES) {
forbidden(conn);
} else {
not_found(conn);
}
return;
}
// 获取文件信息
if (fstat(conn->file_fd, &st) == -1) {
close(conn->file_fd);
not_found(conn);
return;
}
conn->file_size = st.st_size;
conn->file_sent = 0;
conn->response_code = 200;
// 准备响应头
send_headers(conn);
}
// 发送响应头
void send_headers(connection_t *conn) {
char buf[BUFFER_SIZE];
const char *content_type = get_mime_type(conn->path);
// 发送状态行
if (conn->response_code == 200) {
sprintf(buf, "HTTP/1.1 200 OK\r\n");
} else if (conn->response_code == 404) {
sprintf(buf, "HTTP/1.1 404 Not Found\r\n");
} else if (conn->response_code == 400) {
sprintf(buf, "HTTP/1.1 400 Bad Request\r\n");
} else if (conn->response_code == 403) {
sprintf(buf, "HTTP/1.1 403 Forbidden\r\n");
} else if (conn->response_code == 501) {
sprintf(buf, "HTTP/1.1 501 Not Implemented\r\n");
} else {
sprintf(buf, "HTTP/1.1 500 Internal Server Error\r\n");
}
// 计算头部长度
conn->buffer_len = 0;
conn->buffer_sent = 0;
// 状态行
size_t len = strlen(buf);
memcpy(conn->buffer + conn->buffer_len, buf, len);
conn->buffer_len += len;
// Server头
len = strlen(SERVER_STRING);
memcpy(conn->buffer + conn->buffer_len, SERVER_STRING, len);
conn->buffer_len += len;
// Content-Type头
sprintf(buf, "Content-Type: %s\r\n", content_type);
len = strlen(buf);
memcpy(conn->buffer + conn->buffer_len, buf, len);
conn->buffer_len += len;
// Content-Length头
if (conn->response_code == 200) {
sprintf(buf, "Content-Length: %ld\r\n", conn->file_size);
} else {
sprintf(buf, "Content-Length: %zu\r\n", conn->buffer_len);
}
len = strlen(buf);
memcpy(conn->buffer + conn->buffer_len, buf, len);
conn->buffer_len += len;
// Connection头 - 改为keep-alive支持持久连接
sprintf(buf, "Connection: keep-alive\r\n");
len = strlen(buf);
memcpy(conn->buffer + conn->buffer_len, buf, len);
conn->buffer_len += len;
// 结束头部
sprintf(buf, "\r\n");
len = strlen(buf);
memcpy(conn->buffer + conn->buffer_len, buf, len);
conn->buffer_len += len;
// 对于非200响应,错误信息已经在buffer中
if (conn->response_code != 200) {
conn->file_fd = -1; // 不需要发送文件
}
}
// 发送响应
void send_response(connection_t *conn, int epoll_fd) {
// 更新活动时间
conn->last_activity = time(NULL);
// 发送头部
if (conn->buffer_sent < conn->buffer_len) {
ssize_t sent = send(conn->fd,
conn->buffer + conn->buffer_sent,
conn->buffer_len - conn->buffer_sent,
MSG_NOSIGNAL);
if (sent < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
close_connection(conn, epoll_fd);
return;
}
} else {
conn->buffer_sent += sent;
}
// 头部尚未发送完成
if (conn->buffer_sent < conn->buffer_len) {
return;
}
}
// 发送文件内容
if (conn->file_fd != -1 && conn->file_sent < conn->file_size) {
// 使用sendfile系统调用提高效率
off_t offset = conn->file_sent;
ssize_t sent = sendfile(conn->fd, conn->file_fd, &offset,
conn->file_size - conn->file_sent);
if (sent < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 可重试的错误
return;
}
perror("sendfile failed");
close_connection(conn, epoll_fd);
return;
} else if (sent == 0) {
// 文件结束
conn->file_sent = conn->file_size;
} else {
conn->file_sent += sent;
}
}
// 检查是否完成
if (conn->file_fd == -1 || conn->file_sent >= conn->file_size) {
if (conn->file_fd != -1) {
close(conn->file_fd);
conn->file_fd = -1;
}
// 重置连接状态以处理下一个请求
memset(conn->buffer, 0, sizeof(conn->buffer));
conn->buffer_len = 0;
conn->buffer_sent = 0;
conn->file_sent = 0;
conn->response_code = 0;
// 修改为监听读事件
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
ev.data.ptr = conn;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &ev) == -1) {
perror("epoll_ctl mod to read");
close_connection(conn, epoll_fd);
}
}
}
// 404 Not Found
void not_found(connection_t *conn) {
conn->response_code = 404;
const char *response =
"<html><head><title>404 Not Found</title></head>"
"<body><h1>404 Not Found</h1>"
"<p>The requested URL was not found on this server.</p>"
"<p><small>Served by jdbhttpd/0.5.0</small></p></body></html>\r\n";
conn->buffer_len = snprintf(conn->buffer, sizeof(conn->buffer), "%s", response);
send_headers(conn);
}
// 400 Bad Request
void bad_request(connection_t *conn) {
conn->response_code = 400;
const char *response =
"<html><head><title>400 Bad Request</title></head>"
"<body><h1>400 Bad Request</h1>"
"<p>Your browser sent a request that this server could not understand.</p>"
"<p><small>Served by jdbhttpd/0.5.0</small></p></body></html>\r\n";
conn->buffer_len = snprintf(conn->buffer, sizeof(conn->buffer), "%s", response);
send_headers(conn);
}
// 403 Forbidden
void forbidden(connection_t *conn) {
conn->response_code = 403;
const char *response =
"<html><head><title>403 Forbidden</title></head>"
"<body><h1>403 Forbidden</h1>"
"<p>Access to this resource is denied.</p>"
"<p><small>Served by jdbhttpd/0.5.0</small></p></body></html>\r\n";
conn->buffer_len = snprintf(conn->buffer, sizeof(conn->buffer), "%s", response);
send_headers(conn);
}
// 501 Not Implemented
void unimplemented(connection_t *conn) {
conn->response_code = 501;
const char *response =
"<html><head><title>501 Not Implemented</title></head>"
"<body><h1>501 Not Implemented</h1>"
"<p>The requested method is not implemented.</p>"
"<p><small>Served by jdbhttpd/0.5.0</small></p></body></html>\r\n";
conn->buffer_len = snprintf(conn->buffer, sizeof(conn->buffer), "%s", response);
send_headers(conn);
}
// 关闭连接
void close_connection(connection_t *conn, int epoll_fd) {
if (conn->closed) return;
log_request(conn);
// 从epoll中移除
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL);
if (conn->file_fd != -1) {
close(conn->file_fd);
}
close(conn->fd);
conn->closed = 1;
// 从全局连接列表中移除
remove_connection(conn);
free(conn);
}
// 从全局连接列表中移除连接
void remove_connection(connection_t *conn) {
pthread_mutex_lock(&conn_mutex);
for (int i = 0; i < MAX_EVENTS; i++) {
if (connections[i] == conn) {
connections[i] = NULL;
break;
}
}
pthread_mutex_unlock(&conn_mutex);
}
// 检查超时连接
void check_timeouts(int epoll_fd) {
time_t now = time(NULL);
pthread_mutex_lock(&conn_mutex);
for (int i = 0; i < MAX_EVENTS; i++) {
if (connections[i] && !connections[i]->closed) {
if (now - connections[i]->last_activity > CONNECTION_TIMEOUT) {
printf("Closing timed out connection: fd=%d\n", connections[i]->fd);
close_connection(connections[i], epoll_fd);
}
}
}
pthread_mutex_unlock(&conn_mutex);
}
// 添加连接到全局列表
void add_connection(connection_t *conn) {
pthread_mutex_lock(&conn_mutex);
for (int i = 0; i < MAX_EVENTS; i++) {
if (connections[i] == NULL) {
connections[i] = conn;
break;
}
}
pthread_mutex_unlock(&conn_mutex);
}
int main(void) {
unsigned short port = 8080;
int server_sock = startup(&port);
printf("HTTP server (epoll) running on port %d\n", port);
// 设置非阻塞
set_nonblocking(server_sock);
// 创建epoll实例
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(server_sock);
exit(1);
}
// 添加服务器socket到epoll
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.ptr = NULL;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_sock, &event) == -1) {
perror("epoll_ctl: server_sock");
close(server_sock);
close(epoll_fd);
exit(1);
}
// 事件循环
struct epoll_event events[MAX_EVENTS];
time_t last_timeout_check = time(NULL);
while (1) {
int timeout_ms = 1000; // 1秒超时
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout_ms);
if (nfds == -1) {
perror("epoll_wait");
continue;
}
// 定期检查超时连接
time_t now = time(NULL);
if (now - last_timeout_check > 1) { // 每秒检查一次
check_timeouts(epoll_fd);
last_timeout_check = now;
}
for (int i = 0; i < nfds; i++) {
// 处理新连接
if (events[i].data.ptr == NULL) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sock = accept(server_sock,
(struct sockaddr *)&client_addr,
&client_len);
if (client_sock == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("accept");
}
continue;
}
// 设置非阻塞
set_nonblocking(client_sock);
// 创建连接结构体
connection_t *conn = calloc(1, sizeof(connection_t));
if (!conn) {
perror("calloc");
close(client_sock);
continue;
}
conn->fd = client_sock;
conn->file_fd = -1;
conn->last_activity = time(NULL); // 记录活动时间
conn->closed = 0;
// 添加到全局列表
add_connection(conn);
// 添加到epoll
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
ev.data.ptr = conn;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev) == -1) {
perror("epoll_ctl: client_sock");
close_connection(conn, epoll_fd);
}
} else {
// 处理客户端事件
connection_t *conn = events[i].data.ptr;
// 检查连接是否已关闭
if (conn->closed) {
continue;
}
// 检查连接是否关闭
if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
close_connection(conn, epoll_fd);
continue;
}
if (events[i].events & EPOLLIN) {
// 更新活动时间
conn->last_activity = time(NULL);
// 读取数据
ssize_t count = recv(conn->fd,
conn->buffer + conn->buffer_len,
sizeof(conn->buffer) - conn->buffer_len - 1,
0);
if (count > 0) {
conn->buffer_len += count;
conn->buffer[conn->buffer_len] = '\0';
// 检查是否收到完整请求
if (strstr(conn->buffer, "\r\n\r\n") != NULL) {
// 处理请求
handle_request(conn);
// 修改为监听写事件
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET | EPOLLRDHUP;
ev.data.ptr = conn;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &ev) == -1) {
perror("epoll_ctl: mod write");
close_connection(conn, epoll_fd);
}
}
} else if (count == 0 || (count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
// 连接关闭或出错
close_connection(conn, epoll_fd);
}
}
else if (events[i].events & EPOLLOUT) {
// 发送响应
send_response(conn, epoll_fd);
}
}
}
}
close(server_sock);
close(epoll_fd);
return 0;
}
这一版webserver偶尔会出现某一资源长时间加载不出,随后后面的GET请求都无法响应,请分析原因并优化
最新发布