// webserver_epoll.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define PORT 80
#define MAX_EVENTS 100
#define BUFFER_SIZE 4096
#define WEBROOT "./webroot"
// 获取文件扩展名对应的Content-Type
const char* get_content_type(const char* path) {
const char *ext = strrchr(path, '.');
if (!ext) return "text/plain";
if (strcmp(ext, ".html") == 0 || 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, ".png") == 0)
return "image/png";
if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(ext, ".gif") == 0)
return "image/gif";
if (strcmp(ext, ".ico") == 0)
return "image/x-icon";
return "text/plain";
}
void handle_request(int client_socket) {
char buffer[BUFFER_SIZE] = {0};
read(client_socket, buffer, BUFFER_SIZE);
char method[10], path[1024];
sscanf(buffer, "%s %s", method, path);
if (strcmp(method, "GET") == 0) {
char file_path[2048];
snprintf(file_path, sizeof(file_path), "%s%s", WEBROOT, path);
// 处理目录请求
if (path[strlen(path) - 1] == '/') {
strcat(file_path, "Index.html");
}
// 检查文件是否存在
struct stat file_stat;
if (stat(file_path, &file_stat) < 0 || !S_ISREG(file_stat.st_mode)) {
// 尝试添加index.html
// 特殊处理 favicon.ico 请求
if (strcmp(path, "/favicon.ico") == 0) {
// 文件不存在时返回 204 No Content
const char *response =
"HTTP/1.1 204 No Content\r\n"
"Connection: close\r\n"
"\r\n";
send(client_socket, response, strlen(response), 0);
}
if (stat(file_path, &file_stat) < 0) {
printf("File does not exist: %s (%s)\n", file_path, strerror(errno));
char *response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<h1>404 Not Found</h1>";
write(client_socket, response, strlen(response));
close(client_socket);
pthread_exit(NULL);
}
}
// 获取正确的Content-Type
const char *content_type = get_content_type(file_path);
// 打开文件
FILE *file = fopen(file_path, "rb"); // 二进制模式
if (!file) {
printf("Could not open file: %s (%s)\n", file_path, strerror(errno));
char *response = "HTTP/1.1 500 Internal Server Error\r\n\r\n";
write(client_socket, response, strlen(response));
close(client_socket);
pthread_exit(NULL);
}
// 发送HTTP头
char header[BUFFER_SIZE];
int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n"
"Connection: close\r\n\r\n",
content_type, file_stat.st_size);
write(client_socket, header, header_len);
// 发送文件内容
size_t bytes_sent = 0;
while (bytes_sent < file_stat.st_size) {
size_t to_read = file_stat.st_size - bytes_sent;
if (to_read > BUFFER_SIZE) to_read = BUFFER_SIZE;
size_t read_bytes = fread(buffer, 1, to_read, file);
if (read_bytes <= 0) break;
write(client_socket, buffer, read_bytes);
bytes_sent += read_bytes;
}
fclose(file);
}
else if (strcmp(method, "POST") == 0) {
char *content = strstr(buffer, "\r\n\r\n") + 4;
char response[BUFFER_SIZE];
snprintf(response, sizeof(response),
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><body>"
"<h1>POST Received</h1>"
"<p>Data: %s</p>"
"</body></html>", content);
send(client_socket, response, strlen(response), 0);
}
close(client_socket);
}
int main()
{
int server_fd, epoll_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
// 创建TCP套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置非阻塞
int flags = fcntl(server_fd, F_GETFL, 0);
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
// 绑定端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 10) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
printf("Epoll server listening on port %d\n", PORT);
struct epoll_event events[MAX_EVENTS];
while (1) {
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events == -1) {
perror("epoll_wait");
continue;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == server_fd) {
// 接受新连接
int new_socket;
while ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) > 0) {
// 设置新连接为非阻塞
int flags = fcntl(new_socket, F_GETFL, 0);
fcntl(new_socket, F_SETFL, flags | O_NONBLOCK);
event.events = EPOLLIN | EPOLLET;
event.data.fd = new_socket;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event);
}
} else {
// 处理客户端请求
handle_request(events[i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
}
}
}
close(server_fd);
return 0;
}
以上代码实现使用IO多路复用epoll搭建http服务器,但总是在缺少favicon.ico文件时退出,请修改一下
最新发布