#include // 提供网络地址转换函数 #include // 定义错误码 #include // 文件操作相关函数 #include // 定义网络协议的结构体和函数 #include // 提供信号处理函数 #include // 标准输入输出 #include // 标准库函数,如exit() #include // 字符串操作函数 #include // 套接字相关函数 #include // 提供 gettimeofday 函数 #include // 定义数据类型 #include // 时间相关函数 #include // 提供POSIX API,如read()、write() #define VERSION 23 // 服务器版本号 #define BUFSIZE 8096 // 缓冲区大小 #define ERROR 42 // 错误日志类型 #define LOG 44 // 普通日志类型 #define FORBIDDEN 403 // HTTP状态码:禁止访问 #define NOTFOUND 404 // HTTP状态码:未找到资源 #ifndef SIGCLD #define SIGCLD SIGCHLD // 为兼容不同系统定义信号别名 #endif // 支持的文件扩展名及其对应的MIME类型 struct { char *ext; // 文件扩展名 char *filetype; // MIME类型 } extensions[] = { {"gif", "image/gif"}, {"jpg", "image/jpg"}, {"jpeg", "image/jpeg"}, {"png", "image/png"}, {"ico", "image/ico"}, {"zip", "image/zip"}, {"gz", "image/gz"}, {"tar", "image/tar"}, {"htm", "text/html"}, {"html", "text/html"}, {0, 0} // 结束标志 }; /* Ctrl+C信号处理函数 */ void ctrlc_handler(int sig) { printf("您按下了Ctrl+C终止了Web服务器\n"); exit(0); } // 计算两个时间点之间的时间差(以毫秒为单位) long calculate_time_diff(struct timespec start, struct timespec end) { long diff_sec = end.tv_sec - start.tv_sec; // 秒部分差值 long diff_nsec = end.tv_nsec - start.tv_nsec; // 纳秒部分差值 return diff_sec * 1000 + diff_nsec / 1000000.0; // 转换为毫秒 } void logger(int type, const char *s1, const char *s2, int socket_fd) { int fd; char logbuffer[BUFSIZE * 2]; char timebuffer[64]; // 用于存储格式化时间字符串 // 获取当前时间(秒和微秒) struct timeval tv; gettimeofday(&tv, NULL); // 将秒部分格式化为字符串(年月日 时分秒) struct tm *tm_info = localtime(&tv.tv_sec); strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info); // 添加毫秒部分到时间字符串 char full_timebuffer[256]; snprintf(full_timebuffer, sizeof(full_timebuffer), "[%s.%03ld]", timebuffer, tv.tv_usec / 1000); // 根据日志类型生成日志内容 switch (type) { case ERROR: // 错误日志 snprintf(logbuffer, sizeof(logbuffer), "%s [ERROR] %s:%s Errno=%d exiting pid=%d", full_timebuffer, s1, s2, errno, getpid()); break; case FORBIDDEN: // 403 禁止访问 if (write(socket_fd, "HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: " "close\nContent-Type: text/html\n\n" "\n403 " "Forbidden\n\n

Forbidden

\n" "The requested URL, file type or operation is not allowed on this simple " "static file webserver.\n" "\n", 271) < 0) { perror("Failed to send 403 Forbidden response"); } snprintf(logbuffer, sizeof(logbuffer), "%s [FORBIDDEN] %s:%s", full_timebuffer, s1, s2); break; case NOTFOUND: // 404 未找到 if (write(socket_fd, "HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: " "close\nContent-Type: text/html\n\n" "\n404 Not Found\n\n

Not " "Found

\n" "The requested URL was not found on this server.\n\n", 224) < 0) { perror("Failed to send 404 Not Found response"); } snprintf(logbuffer, sizeof(logbuffer), "%s [NOT FOUND] %s:%s", full_timebuffer, s1, s2); break; case LOG: // 普通日志 snprintf(logbuffer, sizeof(logbuffer), "%s [INFO] %s:%s:%d", full_timebuffer, s1, s2, socket_fd); break; } // 打开日志文件并写入日志内容 #ifdef DEBUG if ((fd = open("webserver.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) { if (write(fd, logbuffer, strlen(logbuffer)) < 0) { perror("Failed to write log to file"); } if (write(fd, "\n", 1) < 0) { perror("Failed to write newline to log file"); } close(fd); } else { perror("Failed to open log file"); } #endif } // 处理HTTP请求的函数 void web(int fd, int hit) { int j, file_fd, buflen; long i, ret, len; char *fstr; static char buffer[BUFSIZE + 1]; // 静态缓冲区,自动初始化为0 // 定义计时变量 struct timespec start, end; long duration; logger(LOG, "Process new web request", "", fd); // 记录开始时间(读取请求) clock_gettime(CLOCK_MONOTONIC, &start); ret = read(fd, buffer, BUFSIZE); // 读取客户端请求 if (ret <= 0) { // 读取失败 logger(FORBIDDEN, "Failed to read browser request", "", fd); return; } buffer[ret]='\0'; #ifdef DEBUG printf("第%d个客户请求:%s\n",hit,buffer); #endif // 记录结束时间并计算耗时 clock_gettime(CLOCK_MONOTONIC, &end); duration = calculate_time_diff(start, end); logger(LOG, "Time taken for request reading", "", duration); // 清除换行符和回车符 for (i = 0; i < ret; i++) { if (buffer[i] == '\r' || buffer[i] == '\n') buffer[i] = '*'; } // 记录开始时间(解析请求) clock_gettime(CLOCK_MONOTONIC, &start); if (strncmp(buffer, "GET ", 4)) { // 检查是否为GET请求 logger(FORBIDDEN, "Only simple GET operation supported", buffer, fd); return; } logger(LOG, "request", buffer, hit); for (i = 4; i < BUFSIZE; i++) { // 截取文件路径 if (buffer[i] == ' ') { buffer[i] = 0; break; } } clock_gettime(CLOCK_MONOTONIC, &end); duration = calculate_time_diff(start, end); logger(LOG, "Time taken for request parsing", "", duration); // 检查路径是否合法 for (j = 0; j < i - 1; j++) { if (buffer[j] == '.' && buffer[j + 1] == '.') { logger(FORBIDDEN, "Parent directory (..) path names not supported", buffer, fd); return; } } // 默认返回 index.html if (!strncmp(&buffer[0], "GET /\0", 6)) strcpy(buffer, "GET /index.html"); // 文件类型解析 buflen = strlen(buffer); fstr = 0; clock_gettime(CLOCK_MONOTONIC, &start); for (i = 0; extensions[i].ext != 0; i++) { len = strlen(extensions[i].ext); if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) { fstr = extensions[i].filetype; break; } } clock_gettime(CLOCK_MONOTONIC, &end); duration = calculate_time_diff(start, end); logger(LOG, "Time taken for file type detection", "", duration); if (fstr == 0) { logger(FORBIDDEN, "File extension type not supported", buffer, fd); return; } // 文件打开 clock_gettime(CLOCK_MONOTONIC, &start); if ((file_fd = open(&buffer[5], O_RDONLY)) == -1) { logger(NOTFOUND, "Failed to open file", &buffer[5], fd); return; } clock_gettime(CLOCK_MONOTONIC, &end); duration = calculate_time_diff(start, end); logger(LOG, "Time taken for file opening", "", duration); // 文件读取与传输 logger(LOG, "SEND", &buffer[5], hit); clock_gettime(CLOCK_MONOTONIC, &start); len = (long)lseek(file_fd, 0, SEEK_END); lseek(file_fd, 0, SEEK_SET); sprintf(buffer, "HTTP/1.1 200 OK\nServer: nweb/%d.0\nContent-Length: %ld\nConnection: " "close\nContent-Type: %s\n\n", VERSION, len, fstr); logger(LOG, "Header", buffer, hit); if (write(fd, buffer, strlen(buffer)) < 0) { logger(ERROR, "Failed to write HTTP header", "", fd); close(file_fd); return; } // 分块读取文件内容并发送 while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) { if (write(fd, buffer, ret) < 0) { logger(ERROR, "Failed to write file content", "", fd); break; } } clock_gettime(CLOCK_MONOTONIC, &end); duration = calculate_time_diff(start, end); logger(LOG, "Time taken for file transfer", "", duration); #ifdef DEBUG printf("第%d个客户服务器完成\n",hit); #endif // 关闭文件和套接字 close(file_fd); close(fd); } // 主函数,设置服务器并监听客户端连接 int main(int argc, char **argv) { int i, port, listenfd, socketfd, hit; socklen_t length; static struct sockaddr_in cli_addr; // 客户端地址 static struct sockaddr_in serv_addr; // 服务器地址 // 检查命令行参数是否正确 if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) { (void)printf( "hint: webserver Port-Number Top-Directory\t\tversion %d\n\n" "\twebserver is a small and very safe mini web server\n" "\twebserver only servers out file/web pages with extensions named below\n" "\t and only from the named directory or its sub-directories.\n" "\tThere is no fancy features = safe and secure.\n\n" "\tExample: webserver 8181 /home/oslab &\n\n" "\tOnly Supports:", VERSION); for (i = 0; extensions[i].ext != 0; i++) (void)printf(" %s", extensions[i].ext); (void)printf( "\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n" "\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n" "\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n"); exit(0); } /* 设置Ctrl+C信号处理 */ if(signal(SIGINT,ctrlc_handler)==SIG_ERR) (void)printf("错误: 无法设置SIGINT信号处理\n"); if (!strncmp(argv[2], "/", 2) || !strncmp(argv[2], "/etc", 5) || !strncmp(argv[2], "/bin", 5) || !strncmp(argv[2], "/lib", 5) || !strncmp(argv[2], "/tmp", 5) || !strncmp(argv[2], "/usr", 5) || !strncmp(argv[2], "/dev", 5) || !strncmp(argv[2], "/sbin", 6)) { (void)printf("ERROR: Bad top directory %s, see nweb -?\n", argv[2]); exit(3); } // 验证并切换到指定目录 if (chdir(argv[2]) == -1) { perror("Failed to change directory"); exit(4); } port = atoi(argv[1]); if (port < 1 || port > 60000) { fprintf(stderr, "Invalid port number: %d\n", port); exit(1); } // 设置服务器监听套接字 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(1); } serv_addr.sin_family = AF_INET; /* Eliminates "Address already in use" error from bind. */ int optval=1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int)) < 0) return -1; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Bind failed"); exit(1); } if (listen(listenfd, 64) < 0) { perror("Listen failed"); exit(1); } // 接受客户端连接 for (hit = 1;; hit++) { length = sizeof(cli_addr); if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) { logger(ERROR, "system call", "accept", 0); } web(socketfd, hit); } logger(LOG, "webserver exit noramlly", "", 0); return 0; }