weblab finished
This commit is contained in:
43
weblab/Makefile
Normal file
43
weblab/Makefile
Normal file
@ -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
|
||||
99
weblab/webroot/index.html
Normal file
99
weblab/webroot/index.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>简单Web服务器测试页面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.info {
|
||||
background-color: #e8f5e8;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.feature {
|
||||
margin: 15px 0;
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
.success {
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎉 Web服务器运行成功!</h1>
|
||||
|
||||
<div class="info">
|
||||
<p class="success">恭喜!你的C语言Web服务器已经成功运行!</p>
|
||||
<p>这个页面证明了服务器能够正确处理HTTP请求并返回静态网页内容。</p>
|
||||
</div>
|
||||
|
||||
<h2>服务器功能特性:</h2>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✅ HTTP协议支持</h3>
|
||||
<p>服务器实现了基本的HTTP/1.1协议,能够解析GET请求并返回适当的响应。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✅ 配置文件支持</h3>
|
||||
<p>通过webserver.ini文件可以配置服务器端口和网站根目录。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✅ 访问日志记录</h3>
|
||||
<p>服务器会记录所有访问请求的IP地址、时间和请求路径到webserver.log文件。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✅ 多种文件类型支持</h3>
|
||||
<p>支持HTML、CSS、JavaScript、图片等多种MIME类型的文件。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✅ 并发访问支持</h3>
|
||||
<p>服务器能够在不重启的情况下处理多次访问请求。</p>
|
||||
</div>
|
||||
|
||||
<h2>测试建议:</h2>
|
||||
<ul>
|
||||
<li>尝试访问不存在的页面,查看404错误处理</li>
|
||||
<li>检查webserver.log文件中的访问记录</li>
|
||||
<li>修改webserver.ini配置文件并重启服务器</li>
|
||||
<li>在webroot目录中添加更多HTML、CSS、图片文件进行测试</li>
|
||||
</ul>
|
||||
|
||||
<div class="info">
|
||||
<p><strong>访问时间:</strong><span id="datetime"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 显示当前时间
|
||||
document.getElementById('datetime').textContent = new Date().toLocaleString('zh-CN');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
weblab/webserver
Normal file
BIN
weblab/webserver
Normal file
Binary file not shown.
374
weblab/webserver.c
Normal file
374
weblab/webserver.c
Normal file
@ -0,0 +1,374 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <asm-generic/errno-base.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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",
|
||||
"<h1>405 Method Not Allowed</h1>", 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 = "<html><body><h1>404 Not Found</h1><p>The requested page "
|
||||
"was not found.</p></body></html>";
|
||||
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);
|
||||
}
|
||||
8
weblab/webserver.ini
Normal file
8
weblab/webserver.ini
Normal file
@ -0,0 +1,8 @@
|
||||
# Web服务器配置文件
|
||||
# 网站根目录
|
||||
root=./webroot
|
||||
|
||||
# 服务器监听端口
|
||||
port=80
|
||||
|
||||
# 注释:可以修改以上配置来自定义服务器行为
|
||||
3
weblab/webserver.log
Normal file
3
weblab/webserver.log
Normal file
@ -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
|
||||
@ -1,2 +0,0 @@
|
||||
root=c:\adb
|
||||
port=80
|
||||
Reference in New Issue
Block a user