#include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 #define CONFIG_LINE_MAX 256 #define LOG_LINE_MAX 512 #define DEFAULT_PORT 8080 #define DEFAULT_ROOT "./webroot" // 服务器配置结构 typedef struct { char root[256]; int port; } ServerConfig; // 全局变量 ServerConfig config; FILE *log_file = NULL; // 函数声明 void load_config(const char *config_file); void init_log(); void log_request(const char *client_ip, const char *method, const char *path); void handle_client(int client_socket, struct sockaddr_in client_addr); void send_response(int client_socket, const char *status, const char *content_type, const char *body, int body_length); void send_file(int client_socket, const char *file_path); void send_404(int client_socket); char *get_mime_type(const char *file_path); void signal_handler(int sig); int main() { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); // 信号处理 signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); // 加载配置文件 load_config("webserver.ini"); // 初始化日志 init_log(); printf("Starting Web Server...\n"); printf("Root directory: %s\n", config.root); printf("Port: %d\n", config.port); // 创建socket server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { perror("Socket creation failed"); exit(1); } // 设置socket选项以重用地址 int opt = 1; if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); exit(1); } // 配置服务器地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(config.port); // 绑定socket if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); close(server_socket); exit(1); } // 监听连接 if (listen(server_socket, 10) < 0) { perror("Listen failed"); close(server_socket); exit(1); } printf("Server is listening on port %d...\n", config.port); printf("Press Ctrl+C to stop the server.\n\n"); // 主服务循环 while (1) { client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len); if (client_socket < 0) { if (errno == EINTR) { continue; } perror("Accept failed"); continue; } // 处理客户端请求 handle_client(client_socket, client_addr); close(client_socket); } close(server_socket); if (log_file) { fclose(log_file); } return EXIT_SUCCESS; } // 加载配置文件 void load_config(const char *config_file) { FILE *file; char line[CONFIG_LINE_MAX]; char key[128], value[128]; // 设置默认值 strcpy(config.root, DEFAULT_ROOT); config.port = DEFAULT_PORT; file = fopen(config_file, "r"); if (!file) { printf("Warning: Cannot open config file '%s', using defaults.\n", config_file); return; } while (fgets(line, sizeof(line), file)) { // 跳过注释和空行 if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') { continue; } // 解析键值对 if (sscanf(line, "%127[^=]=%127s", key, value) == 2) { // 去除键的空格 char *k = key; while (*k == ' ' || *k == '\t') k++; char *k_end = k + strlen(k) - 1; while (k_end > k && (*k_end == ' ' || *k_end == '\t' || *k_end == '\n' || *k_end == '\r')) { *k_end = '\0'; k_end--; } if (strcmp(k, "root") == 0) { strcpy(config.root, value); } else if (strcmp(k, "port") == 0) { config.port = atoi(value); if (config.port <= 0 || config.port > 65535) { config.port = DEFAULT_PORT; } } } } fclose(file); } // 初始化日志 void init_log() { log_file = fopen("webserver.log", "a"); if (!log_file) { printf("Warning: Cannot open log file, logging to stdout only.\n"); } } // 记录访问日志 void log_request(const char *client_ip, const char *method, const char *path) { time_t now; struct tm *tm_info; char time_str[64]; char log_line[LOG_LINE_MAX]; time(&now); tm_info = localtime(&now); strftime(time_str, sizeof(time_str), "%Y/%m/%d %H:%M:%S", tm_info); snprintf(log_line, sizeof(log_line), "%s IP:%s %s %s\n", time_str, client_ip, method, path); // 输出到控制台 printf("%s", log_line); // 输出到日志文件 if (log_file) { fprintf(log_file, "%s", log_line); fflush(log_file); } } // 处理客户端请求 void handle_client(int client_socket, struct sockaddr_in client_addr) { char buffer[BUFFER_SIZE]; char method[16], path[256], version[16]; char *client_ip = inet_ntoa(client_addr.sin_addr); // 接收HTTP请求 int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0); if (bytes_received <= 0) { return; } buffer[bytes_received] = '\0'; // 解析HTTP请求行 if (sscanf(buffer, "%15s %255s %15s", method, path, version) != 3) { send_404(client_socket); return; } // 记录访问日志 log_request(client_ip, method, path); // 只处理GET请求 if (strcmp(method, "GET") != 0) { send_response(client_socket, "405 Method Not Allowed", "text/html", "

405 Method Not Allowed

", 30); return; } // 构建完整文件路径 char file_path[512]; if (strcmp(path, "/") == 0) { snprintf(file_path, sizeof(file_path), "%s/index.html", config.root); } else { snprintf(file_path, sizeof(file_path), "%s%s", config.root, path); } // 检查文件是否存在 struct stat file_stat; if (stat(file_path, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) { send_file(client_socket, file_path); } else { send_404(client_socket); } } // 发送HTTP响应 void send_response(int client_socket, const char *status, const char *content_type, const char *body, int body_length) { char header[1024]; snprintf(header, sizeof(header), "HTTP/1.1 %s\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n", status, content_type, body_length); send(client_socket, header, strlen(header), 0); if (body && body_length > 0) { send(client_socket, body, body_length, 0); } } // 发送文件 void send_file(int client_socket, const char *file_path) { FILE *file = fopen(file_path, "rb"); if (!file) { send_404(client_socket); return; } // 获取文件大小 fseek(file, 0, SEEK_END); long file_size = ftell(file); fseek(file, 0, SEEK_SET); // 获取MIME类型 char *mime_type = get_mime_type(file_path); // 发送HTTP头 char header[1024]; 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", mime_type, file_size); send(client_socket, header, strlen(header), 0); // 发送文件内容 char file_buffer[4096]; size_t bytes_read; while ((bytes_read = fread(file_buffer, 1, sizeof(file_buffer), file)) > 0) { send(client_socket, file_buffer, bytes_read, 0); } fclose(file); } // 发送404错误 void send_404(int client_socket) { const char *body = "

404 Not Found

The requested page " "was not found.

"; send_response(client_socket, "404 Not Found", "text/html", body, strlen(body)); } // 获取MIME类型 char *get_mime_type(const char *file_path) { const char *ext = strrchr(file_path, '.'); if (!ext) return "application/octet-stream"; if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) { return "text/html"; } else if (strcmp(ext, ".css") == 0) { return "text/css"; } else if (strcmp(ext, ".js") == 0) { return "application/javascript"; } else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) { return "image/jpeg"; } else if (strcmp(ext, ".png") == 0) { return "image/png"; } else if (strcmp(ext, ".gif") == 0) { return "image/gif"; } else if (strcmp(ext, ".txt") == 0) { return "text/plain"; } return "application/octet-stream"; } // 信号处理函数 void signal_handler(int sig) { char *signal_name = (sig == SIGINT) ? "SIGINT" : "SIGTERM"; time_t now; struct tm *tm_info; char time_str[64]; // 获取当前时间 time(&now); tm_info = localtime(&now); strftime(time_str, sizeof(time_str), "%Y/%m/%d %H:%M:%S", tm_info); // 记录关闭信息 printf("\n[%s] Received %s signal. Shutting down server...\n", time_str, signal_name); // 如果日志文件打开,记录关闭信息并关闭 if (log_file) { fprintf(log_file, "[%s] Server shutdown initiated by %s\n", time_str, signal_name); fflush(log_file); fclose(log_file); log_file = NULL; } // 等待1秒以确保所有pending的写操作完成 sleep(1); printf("Server shutdown complete.\n"); exit(0); }