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类型的文件。
+
+
+
+
✅ 并发访问支持
+
服务器能够在不重启的情况下处理多次访问请求。
+
+
+
测试建议:
+
+ - 尝试访问不存在的页面,查看404错误处理
+ - 检查webserver.log文件中的访问记录
+ - 修改webserver.ini配置文件并重启服务器
+ - 在webroot目录中添加更多HTML、CSS、图片文件进行测试
+
+
+
+
+
+
+
+
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