This commit is contained in:
2026-06-18 18:13:12 +08:00
parent 2f42b036a9
commit 25f7ef1967
34 changed files with 2107 additions and 2 deletions

BIN
server-exp3/cgi-bin/add Normal file

Binary file not shown.

27
server-exp3/cgi-bin/add.c Normal file
View File

@@ -0,0 +1,27 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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<p>", content);
sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>",
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);
}

243
server-exp3/common.c Normal file
View File

@@ -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 <hostname, port>
* 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 */

78
server-exp3/common.h Normal file
View File

@@ -0,0 +1,78 @@
/* $begin common.h */
#ifndef __COMMON_H__
#define __COMMON_H__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <ctype.h>
#include <sys/prctl.h>
#include <stdbool.h>
/* 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 */

BIN
server-exp3/example.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
server-exp3/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

13
server-exp3/index.html Normal file
View File

@@ -0,0 +1,13 @@
<html>
<head>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
<title>the example web</title>
</head>
<body>
<H1>webserver test page</H1>
<p>
Not pretty but should prove that webserver works:-)
<p>
<IMG SRC="example.jpg">
</body>
</html>

7
server-exp3/run.log Normal file
View File

@@ -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

View File

6
server-exp3/t1.txt Normal file
View File

@@ -0,0 +1,6 @@
hello
world
dgut
computer
Hello
WORLD

6
server-exp3/t2.txt Normal file
View File

@@ -0,0 +1,6 @@
hello
world
dgut
computer
Hello
WORLD

8
server-exp3/test.html Normal file
View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>a simple page for testing tiny</title>
<head>
<body>
Hello World
</body>
</html>

474
server-exp3/thpool.c Normal file
View File

@@ -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 <AvailabilityMacros.h>
#else
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#endif
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#if defined(__linux__)
#include <sys/prctl.h>
#endif
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#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);
}

222
server-exp3/thpool.h Normal file
View File

@@ -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

BIN
server-exp3/togglec Executable file

Binary file not shown.

30
server-exp3/togglec.c Normal file
View File

@@ -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 <host><port>\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);
}

6
server-exp3/togglecm Normal file
View File

@@ -0,0 +1,6 @@
for i in {1..20}
do
./togglec localhost 12345 < $1
sleep 1
done

BIN
server-exp3/togglest_pool Executable file

Binary file not shown.

113
server-exp3/togglest_pool.c Normal file
View File

@@ -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; i<n; i++)
if(isupper(buf[i]))
buf[i]=tolower(buf[i]);
else if(islower(buf[i]))
buf[i]=toupper(buf[i]);
//sleep(1);
send (conn_sock, buf, n, 0);
}
}
//示例任务
void handle_request(void *arg)
{
param* datap=(param *) arg;
int conn_sock = datap->conn_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 <port>\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;
}

2
server-exp3/urls Normal file
View File

@@ -0,0 +1,2 @@
http://127.0.0.1:9999/index.html
http://127.0.0.1:9999/test.html

BIN
server-exp3/webclient Executable file

Binary file not shown.

55
server-exp3/webclient.c Normal file
View File

@@ -0,0 +1,55 @@
#include <arpa/inet.h> // 提供IP地址转换函数
#include <netinet/in.h> // 提供套接字地址结构定义
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数如exit()
#include <string.h> // 字符串操作函数
#include <sys/socket.h> // 套接字相关函数
#include <sys/types.h> // 数据类型定义
#include <unistd.h> // 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; // 程序正常退出
}

BIN
server-exp3/webserver Executable file

Binary file not shown.

343
server-exp3/webserver.c Normal file
View File

@@ -0,0 +1,343 @@
#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()
#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"
"<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(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;
}

92
server-exp3/webserver.log Normal file
View File

@@ -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“#ÐnÝy±~ì: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

BIN
server-exp3/webserver_pool Executable file

Binary file not shown.

View 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;
}

Binary file not shown.

View File

@@ -0,0 +1,3 @@
taskline.c、taskline.h: 预线程服务器任务管理程序
pool.c、pool.h: 线程池服务器的线程管理和任务管理程序
urls: 记录运行http_load执行时访问哪些网址