4
This commit is contained in:
374
server-exp3/webserver_pool.c
Normal file
374
server-exp3/webserver_pool.c
Normal file
@@ -0,0 +1,374 @@
|
||||
#include <arpa/inet.h> // 提供网络地址转换函数
|
||||
#include <errno.h> // 定义错误码
|
||||
#include <fcntl.h> // 文件操作相关函数
|
||||
#include <netinet/in.h> // 定义网络协议的结构体和函数
|
||||
#include <signal.h> // 提供信号处理函数
|
||||
#include <stdio.h> // 标准输入输出
|
||||
#include <stdlib.h> // 标准库函数,如exit()
|
||||
#include <string.h> // 字符串操作函数
|
||||
#include <sys/socket.h> // 套接字相关函数
|
||||
#include <sys/time.h> // 提供 gettimeofday 函数
|
||||
#include <sys/types.h> // 定义数据类型
|
||||
#include <time.h> // 时间相关函数
|
||||
#include <unistd.h> // 提供POSIX API,如read()、write()
|
||||
#include "thpool.h"
|
||||
|
||||
|
||||
#define VERSION 23 // 服务器版本号
|
||||
#define BUFSIZE 8096 // 缓冲区大小
|
||||
#define ERROR 42 // 错误日志类型
|
||||
#define LOG 44 // 普通日志类型
|
||||
#define FORBIDDEN 403 // HTTP状态码:禁止访问
|
||||
#define NOTFOUND 404 // HTTP状态码:未找到资源
|
||||
#define NTHREADS 4
|
||||
#define SBUFSIZE 16
|
||||
|
||||
#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} // 结束标志
|
||||
};
|
||||
|
||||
typedef struct _param {
|
||||
int hit;
|
||||
int socketfd;
|
||||
} param;
|
||||
|
||||
/* 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"
|
||||
"<html><head>\n<title>403 "
|
||||
"Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n"
|
||||
"The requested URL, file type or operation is not allowed on this simple "
|
||||
"static file webserver.\n"
|
||||
"</body></html>\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"
|
||||
"<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not "
|
||||
"Found</h1>\n"
|
||||
"The requested URL was not found on this server.\n</body></html>\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(void *arg) {
|
||||
int j, file_fd, buflen;
|
||||
long i, ret, len;
|
||||
char *fstr;
|
||||
char buffer[BUFSIZE + 1]; // 改为线程局部栈缓冲区,避免多线程共享同一缓冲区造成竞争
|
||||
param* datap=(param *) arg;
|
||||
int fd = datap->socketfd;
|
||||
int hit = datap->hit;
|
||||
|
||||
// 定义计时变量
|
||||
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
|
||||
|
||||
// 关闭文件和套接字
|
||||
free(arg);
|
||||
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);
|
||||
}
|
||||
|
||||
printf("start main\n");
|
||||
// 初始化线程池
|
||||
thpool_* pool = thpool_init(NTHREADS);
|
||||
|
||||
// 接受客户端连接
|
||||
for (hit = 1;; hit++) {
|
||||
length = sizeof(cli_addr);
|
||||
if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) {
|
||||
logger(ERROR, "system call", "accept", 0);
|
||||
}
|
||||
|
||||
param *new_param = (param*) malloc(sizeof(param));
|
||||
new_param->hit = hit;
|
||||
new_param->socketfd=socketfd;
|
||||
|
||||
thpool_add_work(pool,web, (void*)new_param);
|
||||
}
|
||||
|
||||
// 等待线程池中的所有任务执行完毕
|
||||
thpool_wait(pool);
|
||||
|
||||
// 销毁线程池
|
||||
thpool_destroy(pool);
|
||||
logger(LOG, "webserver exit noramlly", "", 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user