diff --git a/weblab/Makefile b/weblab/Makefile new file mode 100644 index 0000000..50195d0 --- /dev/null +++ b/weblab/Makefile @@ -0,0 +1,43 @@ +# Makefile for Web Server + +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 +TARGET = webserver +SOURCE = webserver.c + +# 默认目标 +all: $(TARGET) + +# 编译web服务器 +$(TARGET): $(SOURCE) + $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) + +# 创建webroot目录和示例文件 +setup: + mkdir -p webroot + @echo "Creating webroot directory and copying index.html..." + +# 运行服务器 +run: $(TARGET) + ./$(TARGET) + +# 清理编译文件 +clean: + rm -f $(TARGET) + rm -f webserver.log + +# 完全清理(包括webroot目录) +distclean: clean + rm -rf webroot + +# 帮助信息 +help: + @echo "Available targets:" + @echo " all - Compile the web server" + @echo " setup - Create webroot directory" + @echo " run - Compile and run the server" + @echo " clean - Remove compiled files and logs" + @echo " distclean- Remove everything including webroot" + @echo " help - Show this help message" + +.PHONY: all setup run clean distclean help diff --git a/weblab/webroot/index.html b/weblab/webroot/index.html new file mode 100644 index 0000000..d2a804a --- /dev/null +++ b/weblab/webroot/index.html @@ -0,0 +1,99 @@ + + + + + + 简单Web服务器测试页面 + + + +
+

🎉 Web服务器运行成功!

+ +
+

恭喜!你的C语言Web服务器已经成功运行!

+

这个页面证明了服务器能够正确处理HTTP请求并返回静态网页内容。

+
+ +

服务器功能特性:

+ +
+

✅ HTTP协议支持

+

服务器实现了基本的HTTP/1.1协议,能够解析GET请求并返回适当的响应。

+
+ +
+

✅ 配置文件支持

+

通过webserver.ini文件可以配置服务器端口和网站根目录。

+
+ +
+

✅ 访问日志记录

+

服务器会记录所有访问请求的IP地址、时间和请求路径到webserver.log文件。

+
+ +
+

✅ 多种文件类型支持

+

支持HTML、CSS、JavaScript、图片等多种MIME类型的文件。

+
+ +
+

✅ 并发访问支持

+

服务器能够在不重启的情况下处理多次访问请求。

+
+ +

测试建议:

+ + +
+

访问时间:

+
+
+ + + + diff --git a/weblab/webserver b/weblab/webserver new file mode 100644 index 0000000..f3197ed Binary files /dev/null and b/weblab/webserver differ diff --git a/weblab/webserver.c b/weblab/webserver.c new file mode 100644 index 0000000..cfccc58 --- /dev/null +++ b/weblab/webserver.c @@ -0,0 +1,374 @@ +#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); +} diff --git a/weblab/webserver.ini b/weblab/webserver.ini new file mode 100644 index 0000000..b235639 --- /dev/null +++ b/weblab/webserver.ini @@ -0,0 +1,8 @@ +# Web服务器配置文件 +# 网站根目录 +root=./webroot + +# 服务器监听端口 +port=80 + +# 注释:可以修改以上配置来自定义服务器行为 diff --git a/weblab/webserver.log b/weblab/webserver.log new file mode 100644 index 0000000..fce0fa1 --- /dev/null +++ b/weblab/webserver.log @@ -0,0 +1,3 @@ +2025/06/08 21:25:57 IP:127.0.0.1 GET / +2025/06/08 21:25:57 IP:127.0.0.1 GET /favicon.ico +[2025/06/08 21:26:15] Server shutdown initiated by SIGINT diff --git a/weblab/www.ini b/weblab/www.ini deleted file mode 100644 index 812b2fc..0000000 --- a/weblab/www.ini +++ /dev/null @@ -1,2 +0,0 @@ -root=c:\adb -port=80