diff --git a/server-exp2/weblet b/server-exp2/weblet index 269d4c9..50a75f7 100755 Binary files a/server-exp2/weblet and b/server-exp2/weblet differ diff --git a/server-exp2/webletsp b/server-exp2/webletsp index 2816b0b..41afc8f 100755 Binary files a/server-exp2/webletsp and b/server-exp2/webletsp differ diff --git a/server-exp2/webletsp.c b/server-exp2/webletsp.c index 073c262..7ce41b7 100644 --- a/server-exp2/webletsp.c +++ b/server-exp2/webletsp.c @@ -50,10 +50,10 @@ typedef void (*sighandler_t) (int sig) ; void Signal(int sig, sighandler_t handler){ struct sigaction sa; - sa.sa_handler = sigchld_handler; + sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // 确保被信号中断的系统调用自动重启 - sigaction(SIGCHLD, &sa, NULL); + sigaction(sig, &sa, NULL); } @@ -80,6 +80,9 @@ int main(int argc, char **argv) } port = atoi(argv[1]); + //设置SIGHLD的信号处理函数,用于收割结束的子进程 + Signal(SIGCHLD, sigchld_handler); + Signal(SIGPIPE, SIG_IGN); listen_sock = open_listen_sock(port); for (hit=1; ; hit++) { //hit为第几次请求,或第几次http事务 diff --git a/server-exp2/webletst b/server-exp2/webletst index 1f4cef4..9f8546a 100755 Binary files a/server-exp2/webletst and b/server-exp2/webletst differ diff --git a/server-exp2/实验2任务报告(多进程和多线程模型)(1).docx b/server-exp2/实验2任务报告(多进程和多线程模型)(1).docx index 21b6958..b9a1cf4 100644 Binary files a/server-exp2/实验2任务报告(多进程和多线程模型)(1).docx and b/server-exp2/实验2任务报告(多进程和多线程模型)(1).docx differ diff --git a/server-exp3/cgi-bin/add b/server-exp3/cgi-bin/add new file mode 100644 index 0000000..6c58671 Binary files /dev/null and b/server-exp3/cgi-bin/add differ diff --git a/server-exp3/cgi-bin/add.c b/server-exp3/cgi-bin/add.c new file mode 100644 index 0000000..2d32e7a --- /dev/null +++ b/server-exp3/cgi-bin/add.c @@ -0,0 +1,27 @@ +#include +#include +#include +#define MAXLINE 8192 + +int main(void) { + char *buf, *p; + char content[MAXLINE]; + int n1=0, n2=0; + + /* Extract the two arguments from standard input */ + scanf("%d&%d", &n1, &n2); + + /* Make the response body */ + sprintf(content, "Welcome to add.com: "); + sprintf(content, "%sTHE Internet addition portal.\r\n

", content); + sprintf(content, "%sThe answer is: %d + %d = %d\r\n

", + content, n1, n2, n1 + n2); + sprintf(content, "%sThanks for visiting!\r\n", content); + + /* Generate the HTTP response */ + printf("Content-length: %d\r\n", (int) strlen(content)); + printf("Content-type: text/html\r\n\r\n"); + printf("%s", content); + fflush(stdout); + exit(0); +} diff --git a/server-exp3/common.c b/server-exp3/common.c new file mode 100644 index 0000000..8c27c5b --- /dev/null +++ b/server-exp3/common.c @@ -0,0 +1,243 @@ +/* $begin common.c */ + +#include "common.h" + + + +/********************************************************************* + * The Rio package - robust I/O functions + **********************************************************************/ +/* + * rio_readn - robustly read n bytes (unbuffered) + */ +/* $begin rio_readn */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = read(fd, bufp, nleft)) < 0) { + if (errno == EINTR) /* interrupted by sig handler return */ + nread = 0; /* and call read() again */ + else + return -1; /* errno set by read() */ + } + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readn */ + +/* + * rio_writen - robustly write n bytes (unbuffered) + */ +/* $begin rio_writen */ +ssize_t rio_writen(int fd, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nwritten; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nwritten = write(fd, bufp, nleft)) <= 0) { + if (errno == EINTR) /* interrupted by sig handler return */ + nwritten = 0; /* and call write() again */ + else + return -1; /* errorno set by write() */ + } + nleft -= nwritten; + bufp += nwritten; + } + return n; +} +/* $end rio_writen */ + + +/* + * rio_read - This is a wrapper for the Unix read() function that + * transfers min(n, rio_cnt) bytes from an internal buffer to a user + * buffer, where n is the number of bytes requested by the user and + * rio_cnt is the number of unread bytes in the internal buffer. On + * entry, rio_read() refills the internal buffer via a call to + * read() if the internal buffer is empty. + */ +/* $begin rio_read */ +static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) +{ + int cnt; + + while (rp->rio_cnt <= 0) { /* refill if buf is empty */ + rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, + sizeof(rp->rio_buf)); + if (rp->rio_cnt < 0) { + if (errno != EINTR) /* interrupted by sig handler return */ + return -1; + } + else if (rp->rio_cnt == 0) /* EOF */ + return 0; + else + rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */ + } + + /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */ + cnt = n; + if (rp->rio_cnt < n) + cnt = rp->rio_cnt; + memcpy(usrbuf, rp->rio_bufptr, cnt); + rp->rio_bufptr += cnt; + rp->rio_cnt -= cnt; + return cnt; +} +/* $end rio_read */ + +/* + * rio_readinitb - Associate a descriptor with a read buffer and reset buffer + */ +/* $begin rio_readinitb */ +void rio_readinitb(rio_t *rp, int fd) +{ + rp->rio_fd = fd; + rp->rio_cnt = 0; + rp->rio_bufptr = rp->rio_buf; +} +/* $end rio_readinitb */ + +/* + * rio_readnb - Robustly read n bytes (buffered) + */ +/* $begin rio_readnb */ +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) +{ + size_t nleft = n; + ssize_t nread; + char *bufp = usrbuf; + + while (nleft > 0) { + if ((nread = rio_read(rp, bufp, nleft)) < 0) { + if (errno == EINTR) /* interrupted by sig handler return */ + nread = 0; /* call read() again */ + else + return -1; /* errno set by read() */ + } + else if (nread == 0) + break; /* EOF */ + nleft -= nread; + bufp += nread; + } + return (n - nleft); /* return >= 0 */ +} +/* $end rio_readnb */ + +/* + * rio_readlineb - robustly read a text line (buffered) + */ +/* $begin rio_readlineb */ +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) +{ + int n, rc; + char c, *bufp = usrbuf; + + for (n = 1; n < maxlen; n++) { + if ((rc = rio_read(rp, &c, 1)) == 1) { + *bufp++ = c; + if (c == '\n') + break; + } else if (rc == 0) { + if (n == 1) + return 0; /* EOF, no data read */ + else + break; /* EOF, some data was read */ + } else + return -1; /* error */ + } + *bufp = 0; + return n; +} +/* $end rio_readlineb */ + +/********************************** + * Wrappers for robust I/O routines + **********************************/ + + +/******************************** + * Client/server helper functions + ********************************/ +/* + * open_client_sock - open connection to server at + * and return a socket descriptor ready for reading and writing. + * Returns -1 and sets errno on Unix error. + * Returns -2 and sets h_errno on DNS (gethostbyname) error. + */ +/* $begin open_client_sock */ +int open_client_sock(char *hostname, int port) +{ + int client_sock; + struct hostent *hp; + struct sockaddr_in serveraddr; + + if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; /* check errno for cause of error */ + + /* Fill in the server's IP address and port */ + if ((hp = gethostbyname(hostname)) == NULL) + return -2; /* check h_errno for cause of error */ + bzero((char *) &serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + bcopy((char *)hp->h_addr_list[0], + (char *)&serveraddr.sin_addr.s_addr, hp->h_length); + serveraddr.sin_port = htons(port); + + /* Establish a connection with the server */ + if (connect(client_sock, (SA *) &serveraddr, sizeof(serveraddr)) < 0) + return -1; + return client_sock; +} +/* $end open_client_sock */ + +/* + * open_listen_sock - open and return a listening socket on port + * Returns -1 and sets errno on Unix error. + */ +/* $begin open_listen_sock */ +int open_listen_sock(int port) +{ + int listen_sock, optval=1; + struct sockaddr_in serveraddr; + + /* Create a socket descriptor */ + if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; + + /* Eliminates "Address already in use" error from bind. */ + if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, + (const void *)&optval , sizeof(int)) < 0) + return -1; + + /* Listen_sock will be an endpoint for all requests to port + on any IP address for this host */ + bzero((char *) &serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); + serveraddr.sin_port = htons((unsigned short)port); + if (bind(listen_sock, (SA *)&serveraddr, sizeof(serveraddr)) < 0) + return -1; + + /* Make it a listening socket ready to accept connection requests */ + if (listen(listen_sock, LISTENQ) < 0) + return -1; + return listen_sock; +} +/* $end open_listen_sock */ + + +/* $end wrapper.c */ + + + + diff --git a/server-exp3/common.h b/server-exp3/common.h new file mode 100644 index 0000000..edc667c --- /dev/null +++ b/server-exp3/common.h @@ -0,0 +1,78 @@ +/* $begin common.h */ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default file permissions are DEF_MODE & ~DEF_UMASK */ +/* $begin createmasks */ +#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH +#define DEF_UMASK S_IWGRP|S_IWOTH +/* $end createmasks */ + +/* Simplifies calls to bind(), connect(), and accept() */ +/* $begin sockaddrdef */ +typedef struct sockaddr SA; +/* $end sockaddrdef */ + +/* Persistent state for the robust I/O (Rio) package */ +/* $begin rio_t */ +#define RIO_BUFSIZE 8192 +typedef struct { + int rio_fd; /* descriptor for this internal buf */ + int rio_cnt; /* unread bytes in internal buf */ + char *rio_bufptr; /* next unread byte in internal buf */ + char rio_buf[RIO_BUFSIZE]; /* internal buffer */ +} rio_t; +/* $end rio_t */ + +/* External variables */ +extern int h_errno; /* defined by BIND for DNS errors */ +extern char **environ; /* defined by libc */ + +/* Misc constants */ +#define MAXLINE 8192 /* max text line length */ +#define MAXBUF 8192 /* max I/O buffer size */ +#define LISTENQ 1024 /* second argument to listen() */ + +/* Rio (Robust I/O) package */ +ssize_t rio_readn(int fd, void *usrbuf, size_t n); +ssize_t rio_writen(int fd, void *usrbuf, size_t n); +void rio_readinitb(rio_t *rp, int fd); +ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); +ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); + +/* Client/server helper functions */ +int open_client_sock(char *hostname, int portno); +int open_listen_sock(int portno); + +#endif /* __COMMON_H__ */ +/* $end common.h */ diff --git a/server-exp3/example.jpg b/server-exp3/example.jpg new file mode 100644 index 0000000..3b09138 Binary files /dev/null and b/server-exp3/example.jpg differ diff --git a/server-exp3/favicon.ico b/server-exp3/favicon.ico new file mode 100644 index 0000000..b8c2f89 Binary files /dev/null and b/server-exp3/favicon.ico differ diff --git a/server-exp3/index.html b/server-exp3/index.html new file mode 100644 index 0000000..fcc9db0 --- /dev/null +++ b/server-exp3/index.html @@ -0,0 +1,13 @@ + + + + the example web + + +

webserver test page

+

+ Not pretty but should prove that webserver works:-) +

+ + + diff --git a/server-exp3/run.log b/server-exp3/run.log new file mode 100644 index 0000000..1212f43 --- /dev/null +++ b/server-exp3/run.log @@ -0,0 +1,7 @@ +235735 fetches, 5 max parallel, 4.51969e+07 bytes, in 20 seconds +191.728 mean bytes/connection +11786.7 fetches/sec, 2.25985e+06 bytes/sec +msecs/connect: 0.0682447 mean, 1.824 max, 0.018 min +msecs/first-response: 0.323851 mean, 2.544 max, 0.054 min +HTTP response codes: + code 200 -- 235735 diff --git a/server-exp3/server_pool.log b/server-exp3/server_pool.log new file mode 100644 index 0000000..e69de29 diff --git a/server-exp3/t1.txt b/server-exp3/t1.txt new file mode 100644 index 0000000..f885819 --- /dev/null +++ b/server-exp3/t1.txt @@ -0,0 +1,6 @@ +hello +world +dgut +computer +Hello +WORLD diff --git a/server-exp3/t2.txt b/server-exp3/t2.txt new file mode 100644 index 0000000..f885819 --- /dev/null +++ b/server-exp3/t2.txt @@ -0,0 +1,6 @@ +hello +world +dgut +computer +Hello +WORLD diff --git a/server-exp3/test.html b/server-exp3/test.html new file mode 100644 index 0000000..9161126 --- /dev/null +++ b/server-exp3/test.html @@ -0,0 +1,8 @@ + + +a simple page for testing tiny + + +Hello World + + \ No newline at end of file diff --git a/server-exp3/thpool.c b/server-exp3/thpool.c new file mode 100644 index 0000000..1b4d079 --- /dev/null +++ b/server-exp3/thpool.c @@ -0,0 +1,474 @@ +/* ******************************** + * Author: Johan Hanssen Seferidis + * License: MIT + * Description: Library providing a threading pool where you can add + * work. For usage, check the thpool.h file or README.md + * + */ +/** @file thpool.h */ /* + * + ********************************/ + +#if defined(__APPLE__) +#include +#else +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) +#include +#endif +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#endif + +#include "thpool.h" + +#ifdef THPOOL_DEBUG +#define THPOOL_DEBUG 1 +#else +#define THPOOL_DEBUG 0 +#endif + +#if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG) +#define err(str) fprintf(stderr, str) +#else +#define err(str) +#endif + +#ifndef THPOOL_THREAD_NAME +#define THPOOL_THREAD_NAME thpool +#endif + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +static volatile int threads_keepalive; +static volatile int threads_on_hold; + +/* ========================== STRUCTURES ============================ */ + + +/* ========================== PROTOTYPES ============================ */ + +static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id); +static void* thread_do(struct thread* thread_p); +static void thread_hold(int sig_id); +static void thread_destroy(struct thread* thread_p); + +static int jobqueue_init(jobqueue* jobqueue_p); +static void jobqueue_clear(jobqueue* jobqueue_p); +static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p); +static struct job* jobqueue_pull(jobqueue* jobqueue_p); +static void jobqueue_destroy(jobqueue* jobqueue_p); + +static void bsem_init(struct bsem* bsem_p, int value); +static void bsem_reset(struct bsem* bsem_p); +static void bsem_post(struct bsem* bsem_p); +static void bsem_post_all(struct bsem* bsem_p); +static void bsem_wait(struct bsem* bsem_p); + +/* ========================== THREADPOOL ============================ */ + +/* Initialise thread pool */ +struct thpool_* thpool_init(int num_threads) { + threads_on_hold = 0; + threads_keepalive = 1; + + if (num_threads < 0) { + num_threads = 0; + } + + /* Make new thread pool */ + thpool_* thpool_p; + thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_)); + if (thpool_p == NULL) { + err("thpool_init(): Could not allocate memory for thread pool\n"); + return NULL; + } + thpool_p->num_threads_alive = 0; + thpool_p->num_threads_working = 0; + + /* Initialise the job queue */ + if (jobqueue_init(&thpool_p->jobqueue) == -1) { + err("thpool_init(): Could not allocate memory for job queue\n"); + free(thpool_p); + return NULL; + } + + /* Make threads in pool */ + thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread*)); + if (thpool_p->threads == NULL) { + err("thpool_init(): Could not allocate memory for threads\n"); + jobqueue_destroy(&thpool_p->jobqueue); + free(thpool_p); + return NULL; + } + + pthread_mutex_init(&(thpool_p->thcount_lock), NULL); + pthread_cond_init(&thpool_p->threads_all_idle, NULL); + + /* Thread init */ + int n; + for (n = 0; n < num_threads; n++) { + thread_init(thpool_p, &thpool_p->threads[n], n); +#if THPOOL_DEBUG + printf("THPOOL_DEBUG: Created thread %d in pool \n", n); +#endif + } + + /* Wait for threads to initialize */ + while (thpool_p->num_threads_alive != num_threads) { + } + + return thpool_p; +} + +/* Add work to the thread pool */ +int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p) { + job* newjob; + + newjob = (struct job*)malloc(sizeof(struct job)); + if (newjob == NULL) { + err("thpool_add_work(): Could not allocate memory for new job\n"); + return -1; + } + + /* add function and argument */ + newjob->function = function_p; + newjob->arg = arg_p; + + /* add job to queue */ + jobqueue_push(&thpool_p->jobqueue, newjob); + + return 0; +} + +/* Wait until all jobs have finished */ +void thpool_wait(thpool_* thpool_p) { + pthread_mutex_lock(&thpool_p->thcount_lock); + while (thpool_p->jobqueue.len || thpool_p->num_threads_working) { + pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock); + } + pthread_mutex_unlock(&thpool_p->thcount_lock); +} + +/* Destroy the threadpool */ +void thpool_destroy(thpool_* thpool_p) { + /* No need to destroy if it's NULL */ + if (thpool_p == NULL) return; + + volatile int threads_total = thpool_p->num_threads_alive; + + /* End each thread 's infinite loop */ + threads_keepalive = 0; + + /* Give one second to kill idle threads */ + double TIMEOUT = 1.0; + time_t start, end; + double tpassed = 0.0; + time(&start); + while (tpassed < TIMEOUT && thpool_p->num_threads_alive) { + bsem_post_all(thpool_p->jobqueue.has_jobs); + time(&end); + tpassed = difftime(end, start); + } + + /* Poll remaining threads */ + while (thpool_p->num_threads_alive) { + bsem_post_all(thpool_p->jobqueue.has_jobs); + sleep(1); + } + + /* Job queue cleanup */ + jobqueue_destroy(&thpool_p->jobqueue); + /* Deallocs */ + int n; + for (n = 0; n < threads_total; n++) { + thread_destroy(thpool_p->threads[n]); + } + free(thpool_p->threads); + free(thpool_p); +} + +/* Pause all threads in threadpool */ +void thpool_pause(thpool_* thpool_p) { + int n; + for (n = 0; n < thpool_p->num_threads_alive; n++) { + pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1); + } +} + +/* Resume all threads in threadpool */ +void thpool_resume(thpool_* thpool_p) { + // resuming a single threadpool hasn't been + // implemented yet, meanwhile this suppresses + // the warnings + (void)thpool_p; + + threads_on_hold = 0; +} + +int thpool_num_threads_working(thpool_* thpool_p) { return thpool_p->num_threads_working; } + +/* ============================ THREAD ============================== */ + +/* Initialize a thread in the thread pool + * + * @param thread address to the pointer of the thread to be created + * @param id id to be given to the thread + * @return 0 on success, -1 otherwise. + */ +static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id) { + *thread_p = (struct thread*)malloc(sizeof(struct thread)); + if (*thread_p == NULL) { + err("thread_init(): Could not allocate memory for thread\n"); + return -1; + } + + (*thread_p)->thpool_p = thpool_p; + (*thread_p)->id = id; + + pthread_create(&(*thread_p)->pthread, NULL, (void* (*)(void*))thread_do, (*thread_p)); + pthread_detach((*thread_p)->pthread); + return 0; +} + +/* Sets the calling thread on hold */ +static void thread_hold(int sig_id) { + (void)sig_id; + threads_on_hold = 1; + while (threads_on_hold) { + sleep(1); + } +} + +/* What each thread is doing + * + * In principle this is an endless loop. The only time this loop gets interrupted is once + * thpool_destroy() is invoked or the program exits. + * + * @param thread thread that will run this function + * @return nothing + */ +static void* thread_do(struct thread* thread_p) { + /* Set thread name for profiling and debugging */ + char thread_name[16] = {0}; + + snprintf(thread_name, 16, TOSTRING(THPOOL_THREAD_NAME) "-%d", thread_p->id); + +#if defined(__linux__) + /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ + prctl(PR_SET_NAME, thread_name); +#elif defined(__APPLE__) && defined(__MACH__) + pthread_setname_np(thread_name); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(thread_p->pthread, thread_name); +#else + err("thread_do(): pthread_setname_np is not supported on this system"); +#endif + + /* Assure all threads have been created before starting serving */ + thpool_* thpool_p = thread_p->thpool_p; + + /* Register signal handler */ + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_ONSTACK; + act.sa_handler = thread_hold; + if (sigaction(SIGUSR1, &act, NULL) == -1) { + err("thread_do(): cannot handle SIGUSR1"); + } + + /* Mark thread as alive (initialized) */ + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_alive += 1; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + while (threads_keepalive) { + bsem_wait(thpool_p->jobqueue.has_jobs); + + if (threads_keepalive) { + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_working++; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + /* Read job from queue and execute it */ + void (*func_buff)(void*); + void* arg_buff; + job* job_p = jobqueue_pull(&thpool_p->jobqueue); + if (job_p) { + #ifdef DEBUG + printf("线程%d开始处理\n",thread_p->id); + #endif + func_buff = job_p->function; + arg_buff = job_p->arg; + func_buff(arg_buff); + + #ifdef DEBUG + printf("线程%d处理完成\n",thread_p->id); + #endif + free(job_p); + } + + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_working--; + if (!thpool_p->num_threads_working) { + pthread_cond_signal(&thpool_p->threads_all_idle); + } + pthread_mutex_unlock(&thpool_p->thcount_lock); + } + } + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_alive--; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + return NULL; +} + +/* Frees a thread */ +static void thread_destroy(thread* thread_p) { free(thread_p); } + +/* ============================ JOB QUEUE =========================== */ + +/* Initialize queue */ +static int jobqueue_init(jobqueue* jobqueue_p) { + jobqueue_p->len = 0; + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + + jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem)); + if (jobqueue_p->has_jobs == NULL) { + return -1; + } + + pthread_mutex_init(&(jobqueue_p->rwmutex), NULL); + bsem_init(jobqueue_p->has_jobs, 0); + + return 0; +} + +/* Clear the queue */ +static void jobqueue_clear(jobqueue* jobqueue_p) { + while (jobqueue_p->len) { + free(jobqueue_pull(jobqueue_p)); + } + + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + bsem_reset(jobqueue_p->has_jobs); + jobqueue_p->len = 0; +} + +/* Add (allocated) job to queue + */ +static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob) { + pthread_mutex_lock(&jobqueue_p->rwmutex); + newjob->prev = NULL; + + switch (jobqueue_p->len) { + case 0: /* if no jobs in queue */ + jobqueue_p->front = newjob; + jobqueue_p->rear = newjob; + break; + + default: /* if jobs in queue */ + jobqueue_p->rear->prev = newjob; + jobqueue_p->rear = newjob; + } + jobqueue_p->len++; + + bsem_post(jobqueue_p->has_jobs); + pthread_mutex_unlock(&jobqueue_p->rwmutex); +} + +/* Get first job from queue(removes it from queue) + * Notice: Caller MUST hold a mutex + */ +static struct job* jobqueue_pull(jobqueue* jobqueue_p) { + pthread_mutex_lock(&jobqueue_p->rwmutex); + job* job_p = jobqueue_p->front; + + switch (jobqueue_p->len) { + case 0: /* if no jobs in queue */ + break; + + case 1: /* if one job in queue */ + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + jobqueue_p->len = 0; + break; + + default: /* if >1 jobs in queue */ + jobqueue_p->front = job_p->prev; + jobqueue_p->len--; + /* more than one job in queue -> post it */ + bsem_post(jobqueue_p->has_jobs); + } + + pthread_mutex_unlock(&jobqueue_p->rwmutex); + return job_p; +} + +/* Free all queue resources back to the system */ +static void jobqueue_destroy(jobqueue* jobqueue_p) { + jobqueue_clear(jobqueue_p); + free(jobqueue_p->has_jobs); +} + +/* ======================== SYNCHRONISATION ========================= */ + +/* Init semaphore to 1 or 0 */ +static void bsem_init(bsem* bsem_p, int value) { + if (value < 0 || value > 1) { + err("bsem_init(): Binary semaphore can take only values 1 or 0"); + exit(1); + } + pthread_mutex_init(&(bsem_p->mutex), NULL); + pthread_cond_init(&(bsem_p->cond), NULL); + bsem_p->v = value; +} + +/* Reset semaphore to 0 */ +static void bsem_reset(bsem* bsem_p) { + pthread_mutex_destroy(&(bsem_p->mutex)); + pthread_cond_destroy(&(bsem_p->cond)); + bsem_init(bsem_p, 0); +} + +/* Post to at least one thread */ +static void bsem_post(bsem* bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + bsem_p->v = 1; + pthread_cond_signal(&bsem_p->cond); + pthread_mutex_unlock(&bsem_p->mutex); +} + +/* Post to all threads */ +static void bsem_post_all(bsem* bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + bsem_p->v = 1; + pthread_cond_broadcast(&bsem_p->cond); + pthread_mutex_unlock(&bsem_p->mutex); +} + +/* Wait on semaphore until semaphore has value 0 */ +static void bsem_wait(bsem* bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + while (bsem_p->v != 1) { + pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex); + } + bsem_p->v = 0; + pthread_mutex_unlock(&bsem_p->mutex); +} diff --git a/server-exp3/thpool.h b/server-exp3/thpool.h new file mode 100644 index 0000000..ab25dfc --- /dev/null +++ b/server-exp3/thpool.h @@ -0,0 +1,222 @@ +/********************************** + * @author Johan Hanssen Seferidis + * License: MIT + * + **********************************/ + +#ifndef _THPOOL_ +#define _THPOOL_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* =================================== API ======================================= */ + +/* ========================== STRUCTURES ============================ */ + +/* Binary semaphore */ +typedef struct bsem { + pthread_mutex_t mutex; + pthread_cond_t cond; + int v; +} bsem; + +/* Job */ +typedef struct job { + struct job* prev; /* pointer to previous job */ + void (*function)(void* arg); /* function pointer */ + void* arg; /* function's argument */ +} job; + +/* Job queue */ +typedef struct jobqueue { + pthread_mutex_t rwmutex; /* used for queue r/w access */ + job* front; /* pointer to front of queue */ + job* rear; /* pointer to rear of queue */ + bsem* has_jobs; /* flag as binary semaphore */ + int len; /* number of jobs in queue */ +} jobqueue; + +/* Thread */ +typedef struct thread { + int id; /* friendly id */ + pthread_t pthread; /* pointer to actual thread */ + struct thpool_* thpool_p; /* access to thpool */ +} thread; + +/* Threadpool */ +typedef struct thpool_ { + thread** threads; /* pointer to threads */ + volatile int num_threads_alive; /* threads currently alive */ + volatile int num_threads_working; /* threads currently working */ + pthread_mutex_t thcount_lock; /* used for thread count etc */ + pthread_cond_t threads_all_idle; /* signal to thpool_wait */ + jobqueue jobqueue; /* job queue */ +} thpool_; + +typedef struct thpool_* threadpool; + +/** + * @brief Initialize threadpool + * + * Initializes a threadpool. This function will not return until all + * threads have initialized successfully. + * + * @example + * + * .. + * threadpool thpool; //First we declare a threadpool + * thpool = thpool_init(4); //then we initialize it to 4 threads + * .. + * + * @param num_threads number of threads to be created in the threadpool + * @return threadpool created threadpool on success, + * NULL on error + */ +threadpool thpool_init(int num_threads); + +/** + * @brief Add work to the job queue + * + * Takes an action and its argument and adds it to the threadpool's job queue. + * If you want to add to work a function with more than one arguments then + * a way to implement this is by passing a pointer to a structure. + * + * NOTICE: You have to cast both the function and argument to not get warnings. + * + * @example + * + * void print_num(int num){ + * printf("%d\n", num); + * } + * + * int main() { + * .. + * int a = 10; + * thpool_add_work(thpool, (void*)print_num, (void*)a); + * .. + * } + * + * @param threadpool threadpool to which the work will be added + * @param function_p pointer to function to add as work + * @param arg_p pointer to an argument + * @return 0 on success, -1 otherwise. + */ +int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p); + +/** + * @brief Wait for all queued jobs to finish + * + * Will wait for all jobs - both queued and currently running to finish. + * Once the queue is empty and all work has completed, the calling thread + * (probably the main program) will continue. + * + * Smart polling is used in wait. The polling is initially 0 - meaning that + * there is virtually no polling at all. If after 1 seconds the threads + * haven't finished, the polling interval starts growing exponentially + * until it reaches max_secs seconds. Then it jumps down to a maximum polling + * interval assuming that heavy processing is being used in the threadpool. + * + * @example + * + * .. + * threadpool thpool = thpool_init(4); + * .. + * // Add a bunch of work + * .. + * thpool_wait(thpool); + * puts("All added work has finished"); + * .. + * + * @param threadpool the threadpool to wait for + * @return nothing + */ +void thpool_wait(threadpool); + +/** + * @brief Pauses all threads immediately + * + * The threads will be paused no matter if they are idle or working. + * The threads return to their previous states once thpool_resume + * is called. + * + * While the thread is being paused, new work can be added. + * + * @example + * + * threadpool thpool = thpool_init(4); + * thpool_pause(thpool); + * .. + * // Add a bunch of work + * .. + * thpool_resume(thpool); // Let the threads start their magic + * + * @param threadpool the threadpool where the threads should be paused + * @return nothing + */ +void thpool_pause(threadpool); + +/** + * @brief Unpauses all threads if they are paused + * + * @example + * .. + * thpool_pause(thpool); + * sleep(10); // Delay execution 10 seconds + * thpool_resume(thpool); + * .. + * + * @param threadpool the threadpool where the threads should be unpaused + * @return nothing + */ +void thpool_resume(threadpool); + +/** + * @brief Destroy the threadpool + * + * This will wait for the currently active threads to finish and then 'kill' + * the whole threadpool to free up memory. + * + * @example + * int main() { + * threadpool thpool1 = thpool_init(2); + * threadpool thpool2 = thpool_init(2); + * .. + * thpool_destroy(thpool1); + * .. + * return 0; + * } + * + * @param threadpool the threadpool to destroy + * @return nothing + */ +void thpool_destroy(threadpool); + +/** + * @brief Show currently working threads + * + * Working threads are the threads that are performing work (not idle). + * + * @example + * int main() { + * threadpool thpool1 = thpool_init(2); + * threadpool thpool2 = thpool_init(2); + * .. + * printf("Working threads: %d\n", thpool_num_threads_working(thpool1)); + * .. + * return 0; + * } + * + * @param threadpool the threadpool of interest + * @return integer number of threads working + */ +int thpool_num_threads_working(threadpool); +threadpool* initThreadPool(int); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/server-exp3/togglec b/server-exp3/togglec new file mode 100755 index 0000000..65cd6f3 Binary files /dev/null and b/server-exp3/togglec differ diff --git a/server-exp3/togglec.c b/server-exp3/togglec.c new file mode 100644 index 0000000..f3dceb0 --- /dev/null +++ b/server-exp3/togglec.c @@ -0,0 +1,30 @@ +#include "common.h" + +int main(int argc, char **argv) +{ + int client_sock, port; + char *host, buf[MAXLINE]; + rio_t rio; + + if (argc != 3) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + host = argv[1]; + port = atoi(argv[2]); + + client_sock = open_client_sock(host, port); + if(client_sock==-1) { + fputs("Error to connect the Server\n",stdout); + exit(1); + } + + while (fgets(buf, MAXLINE, stdin) != NULL) { + //sleep(1); + send(client_sock, buf, strlen(buf),0); + recv(client_sock, buf, MAXLINE,0); + fputs(buf, stdout); + } + close(client_sock); + exit(0); +} diff --git a/server-exp3/togglecm b/server-exp3/togglecm new file mode 100644 index 0000000..582f702 --- /dev/null +++ b/server-exp3/togglecm @@ -0,0 +1,6 @@ +for i in {1..20} +do + ./togglec localhost 12345 < $1 + sleep 1 +done + diff --git a/server-exp3/togglest_pool b/server-exp3/togglest_pool new file mode 100755 index 0000000..d794cc4 Binary files /dev/null and b/server-exp3/togglest_pool differ diff --git a/server-exp3/togglest_pool.c b/server-exp3/togglest_pool.c new file mode 100644 index 0000000..62d0612 --- /dev/null +++ b/server-exp3/togglest_pool.c @@ -0,0 +1,113 @@ +#include "common.h" +#include "thpool.h" + +#define NTHREADS 4 +#define SBUFSIZE 16 + +typedef struct _param { + int hit; + int conn_sock; +} param; + +void toggle(int conn_sock,int hit); +void handle_request( void *arg) ; + +void toggle(int conn_sock,int hit) +{ + size_t n; int i,no=0; + char buf[MAXLINE]; + + + while((n =recv(conn_sock, buf, MAXLINE,0))> 0) { + printf("toggle服务器收到第%d个客户第%d个消息,长度为%d字节\n", hit,++no,(int)n); + + for(i=0; iconn_sock; + int hit = datap->hit; + + printf("第%d个客户通信开始\n",hit); + //usleep(1); + toggle(conn_sock,hit); + printf("第%d个客户通信结束\n",hit); + + free(arg); + close(conn_sock); +} + +//主函数 +int main(int argc, char **argv) { + + int listen_sock, conn_sock, port,i; + struct sockaddr_in clientaddr; + struct hostent *hp; + char *haddrp; + int hit; + + int nth[NTHREADS]; + + socklen_t clientlen=sizeof(struct sockaddr_in); + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + + listen_sock = open_listen_sock(port); + if( listen_sock==-1) { + printf("端口号%d繁忙\n",port); + exit(1); + } + + port = atoi(argv[1]); + + listen_sock = open_listen_sock(port); + if( listen_sock==-1) { + printf("端口号%d繁忙\n",port); + exit(1); + } + + printf("start main\n"); + // 初始化线程池 + thpool_* pool = thpool_init(NTHREADS); + + // 创建示例任务并将其添加到线程池 + for (hit=1; ; hit++) { + conn_sock = accept(listen_sock, (SA *) &clientaddr, &clientlen); + + //determine the domain name and IP address of the client + hp = gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr, + sizeof(clientaddr.sin_addr.s_addr), AF_INET); + haddrp = inet_ntoa(clientaddr.sin_addr); + //printf("server connected to %s (%s)\n", hp->h_name, haddrp); + + param *new_param = (param*) malloc(sizeof(param)); + + new_param->hit = hit; + new_param->conn_sock=conn_sock; + + thpool_add_work(pool,handle_request, (void*)new_param); + } + + // 等待线程池中的所有任务执行完毕 + thpool_wait(pool); + + // 销毁线程池 + thpool_destroy(pool); + + printf("stop main\n"); + return 0; +} diff --git a/server-exp3/urls b/server-exp3/urls new file mode 100644 index 0000000..7ab9998 --- /dev/null +++ b/server-exp3/urls @@ -0,0 +1,2 @@ +http://127.0.0.1:9999/index.html +http://127.0.0.1:9999/test.html diff --git a/server-exp3/webclient b/server-exp3/webclient new file mode 100755 index 0000000..2a386ff Binary files /dev/null and b/server-exp3/webclient differ diff --git a/server-exp3/webclient.c b/server-exp3/webclient.c new file mode 100644 index 0000000..9f6ef9e --- /dev/null +++ b/server-exp3/webclient.c @@ -0,0 +1,55 @@ +#include // 提供IP地址转换函数 +#include // 提供套接字地址结构定义 +#include // 标准输入输出 +#include // 标准库函数,如exit() +#include // 字符串操作函数 +#include // 套接字相关函数 +#include // 数据类型定义 +#include // POSIX API,如read()和write() + +//#define PORT 8181 /* 目标服务器的端口号 */ +//#define IP_ADDRESS "192.168.0.8" /* 目标服务器的IP地址 */ +#define BUFSIZE 8196 /* 缓冲区大小 */ + +char *command = "GET /index.html HTTP/1.0 \r\n\r\n"; /* HTTP GET 请求命令 */ + +// 错误处理函数,打印错误信息并退出程序 +void pexit(char *msg) { + perror(msg); // 打印错误信息 + exit(1); // 退出程序 +} + +int main(int argc, char *argv[]) {//客户端启动命令为"./client 127.0.0.1 8088",argv[1]是IP地址,argv[2]是端口号8088 + int i, sockfd; // sockfd是套接字文件描述符 + char buffer[BUFSIZE]; // 用于存储从服务器接收的数据 + struct sockaddr_in serv_addr; // 定义服务器地址结构 + + // 打印尝试连接服务器的信息 + printf("客户端尝试连接到 %s 和端口 %s\n", argv[1], argv[2]); + + // 创建套接字,使用IPv4和TCP协议 + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) pexit("socket() 创建失败"); + + // 配置服务器地址 + serv_addr.sin_family = AF_INET; // 地址族为IPv4 + serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置服务器IP地址,如"127.0.0.1" + serv_addr.sin_port = htons(atoi(argv[2])); // 设置服务器端口号,如"8088" + + // 尝试连接到服务器 + if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + pexit("connect() 连接失败"); + + // 发送HTTP GET请求到服务器 + printf("发送字节数=%ld %s\n", strlen(command), command); + if (write(sockfd, command, strlen(command)) < 0) pexit("write() 发送请求失败"); + + // 循环读取服务器返回的数据,并输出到标准输出 + while ((i = read(sockfd, buffer, BUFSIZE)) > 0) { + if (write(1, buffer, i) < 0) // 1表示标准输出 + pexit("write() 输出到标准输出失败"); + } + + // 关闭套接字,释放资源 + close(sockfd); + return 0; // 程序正常退出 +} diff --git a/server-exp3/webserver b/server-exp3/webserver new file mode 100755 index 0000000..57071ac Binary files /dev/null and b/server-exp3/webserver differ diff --git a/server-exp3/webserver.c b/server-exp3/webserver.c new file mode 100644 index 0000000..63c995e --- /dev/null +++ b/server-exp3/webserver.c @@ -0,0 +1,343 @@ +#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; +} diff --git a/server-exp3/webserver.log b/server-exp3/webserver.log new file mode 100644 index 0000000..e37eebc --- /dev/null +++ b/server-exp3/webserver.log @@ -0,0 +1,92 @@ +[2025-04-21 16:17:13.181] [INFO] Process new web request::4 +[2025-04-21 16:17:19.416] [FORBIDDEN] Failed to read browser request: +[2025-04-21 16:17:22.472] [INFO] Process new web request::5 +[2025-04-21 16:17:22.475] [INFO] Time taken for request reading::2 +[2025-04-21 16:17:22.475] [INFO] request:GET /index.html HTTP/1.1**Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8**Upgrade-Insecure-Requests: 1**User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/60.5 Safari/605.1.15**Accept-Encoding: gzip, deflate**Accept-Language: zh-CN,zh;q=0.90**Connection: Keep-Alive**Host: 127.0.0.1:8088**Sec-Fetch-Dest: document**Sec-Fetch-Mode: navigate**Sec-Fetch-Site: none****:2 +[2025-04-21 16:17:22.475] [INFO] Time taken for request parsing::0 +[2025-04-21 16:17:22.475] [INFO] Time taken for file type detection::0 +[2025-04-21 16:17:22.475] [INFO] Time taken for file opening::0 +[2025-04-21 16:17:22.475] [INFO] SEND:index.html:2 +[2025-04-21 16:17:22.475] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 275 +Connection: close +Content-Type: text/html + +:2 +[2025-04-21 16:17:22.489] [INFO] Time taken for file transfer::14 +[2025-04-21 16:17:23.071] [INFO] Process new web request::5 +[2025-04-21 16:17:23.071] [INFO] Time taken for request reading::0 +[2025-04-21 16:17:23.071] [INFO] request:GET /example.jpg HTTP/1.1**Accept: image/webp,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5**Referer: http://127.0.0.1:8088/index.html**User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/60.5 Safari/605.1.15**Accept-Encoding: gzip, deflate**Accept-Language: zh-CN,zh;q=0.90**Connection: Keep-Alive**Host: 127.0.0.1:8088**Sec-Fetch-Dest: image**Sec-Fetch-Mode: no-cors**Sec-Fetch-Site: same-origin****:3 +[2025-04-21 16:17:23.071] [INFO] Time taken for request parsing::0 +[2025-04-21 16:17:23.071] [INFO] Time taken for file type detection::0 +[2025-04-21 16:17:23.071] [INFO] Time taken for file opening::0 +[2025-04-21 16:17:23.071] [INFO] SEND:example.jpg:3 +[2025-04-21 16:17:23.071] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 193852 +Connection: close +Content-Type: image/jpg + +:3 +[2025-04-21 16:17:23.334] [INFO] Time taken for file transfer::262 +[2025-04-21 16:18:42.699] [INFO] Process new web request::5 +[2025-04-21 16:18:42.699] [INFO] Time taken for request reading::0 +[2025-04-21 16:18:42.699] [INFO] request:GET /index.html HTTP/1.0 ****P;aqoգ !}s 'D+DxS^h_q#ny~:4 +[2025-04-21 16:18:42.699] [INFO] Time taken for request parsing::0 +[2025-04-21 16:18:42.699] [INFO] Time taken for file type detection::0 +[2025-04-21 16:18:42.699] [INFO] Time taken for file opening::0 +[2025-04-21 16:18:42.699] [INFO] SEND:index.html:4 +[2025-04-21 16:18:42.699] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 275 +Connection: close +Content-Type: text/html + +:4 +[2025-04-21 16:18:42.699] [INFO] Time taken for file transfer::0 +[2026-06-16 16:27:48.793] [INFO] Process new web request::4 +[2026-06-16 16:27:48.793] [INFO] Time taken for request reading::0 +[2026-06-16 16:27:48.793] [INFO] request:GET /index.html HTTP/1.0 ****:1 +[2026-06-16 16:27:48.793] [INFO] Time taken for request parsing::0 +[2026-06-16 16:27:48.793] [INFO] Time taken for file type detection::0 +[2026-06-16 16:27:48.793] [INFO] Time taken for file opening::0 +[2026-06-16 16:27:48.794] [INFO] SEND:index.html:1 +[2026-06-16 16:27:48.794] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 275 +Connection: close +Content-Type: text/html + +:1 +[2026-06-16 16:27:48.794] [INFO] Time taken for file transfer::0 +[2026-06-16 16:47:59.043] [INFO] Process new web request::4 +[2026-06-16 16:47:59.044] [INFO] Time taken for request reading::0 +[2026-06-16 16:47:59.044] [INFO] request:GET /index.html HTTP/1.0 ****:1 +[2026-06-16 16:47:59.044] [INFO] Time taken for request parsing::0 +[2026-06-16 16:47:59.044] [INFO] Time taken for file type detection::0 +[2026-06-16 16:47:59.044] [INFO] Time taken for file opening::0 +[2026-06-16 16:47:59.044] [INFO] SEND:index.html:1 +[2026-06-16 16:47:59.044] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 275 +Connection: close +Content-Type: text/html + +:1 +[2026-06-16 16:47:59.045] [INFO] Time taken for file transfer::0 +[2026-06-16 17:12:17.415] [INFO] Process new web request::5 +[2026-06-16 17:12:17.415] [INFO] Time taken for request reading::0 +[2026-06-16 17:12:17.416] [INFO] request:GET /index.html HTTP/1.1**Host: 127.0.0.1:8088**User-Agent: curl/8.18.0**Accept: */*****:2 +[2026-06-16 17:12:17.416] [INFO] Time taken for request parsing::0 +[2026-06-16 17:12:17.416] [INFO] Time taken for file type detection::0 +[2026-06-16 17:12:17.416] [INFO] Time taken for file opening::0 +[2026-06-16 17:12:17.416] [INFO] SEND:index.html:2 +[2026-06-16 17:12:17.416] [INFO] Header:HTTP/1.1 200 OK +Server: nweb/23.0 +Content-Length: 275 +Connection: close +Content-Type: text/html + +:2 +[2026-06-16 17:12:17.416] [INFO] Time taken for file transfer::0 diff --git a/server-exp3/webserver_pool b/server-exp3/webserver_pool new file mode 100755 index 0000000..cb4ee7e Binary files /dev/null and b/server-exp3/webserver_pool differ diff --git a/server-exp3/webserver_pool.c b/server-exp3/webserver_pool.c new file mode 100644 index 0000000..b5bdb96 --- /dev/null +++ b/server-exp3/webserver_pool.c @@ -0,0 +1,374 @@ +#include // 提供网络地址转换函数 +#include // 定义错误码 +#include // 文件操作相关函数 +#include // 定义网络协议的结构体和函数 +#include // 提供信号处理函数 +#include // 标准输入输出 +#include // 标准库函数,如exit() +#include // 字符串操作函数 +#include // 套接字相关函数 +#include // 提供 gettimeofday 函数 +#include // 定义数据类型 +#include // 时间相关函数 +#include // 提供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" + "\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(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; +} diff --git a/server-exp3/实验3任务报告.docx b/server-exp3/实验3任务报告.docx new file mode 100644 index 0000000..ebc3ebd Binary files /dev/null and b/server-exp3/实验3任务报告.docx differ diff --git a/server-exp3/实验3文件清单.txt b/server-exp3/实验3文件清单.txt new file mode 100644 index 0000000..b73c57a --- /dev/null +++ b/server-exp3/实验3文件清单.txt @@ -0,0 +1,3 @@ +taskline.c、taskline.h: 预线程服务器任务管理程序 +pool.c、pool.h: 线程池服务器的线程管理和任务管理程序 +urls: 记录运行http_load执行时,访问哪些网址 \ No newline at end of file