4
This commit is contained in:
BIN
server-exp3/cgi-bin/add
Normal file
BIN
server-exp3/cgi-bin/add
Normal file
Binary file not shown.
27
server-exp3/cgi-bin/add.c
Normal file
27
server-exp3/cgi-bin/add.c
Normal 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
243
server-exp3/common.c
Normal 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
78
server-exp3/common.h
Normal 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
BIN
server-exp3/example.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
BIN
server-exp3/favicon.ico
Normal file
BIN
server-exp3/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
13
server-exp3/index.html
Normal file
13
server-exp3/index.html
Normal 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
7
server-exp3/run.log
Normal 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
|
||||
0
server-exp3/server_pool.log
Normal file
0
server-exp3/server_pool.log
Normal file
6
server-exp3/t1.txt
Normal file
6
server-exp3/t1.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
hello
|
||||
world
|
||||
dgut
|
||||
computer
|
||||
Hello
|
||||
WORLD
|
||||
6
server-exp3/t2.txt
Normal file
6
server-exp3/t2.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
hello
|
||||
world
|
||||
dgut
|
||||
computer
|
||||
Hello
|
||||
WORLD
|
||||
8
server-exp3/test.html
Normal file
8
server-exp3/test.html
Normal 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
474
server-exp3/thpool.c
Normal 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
222
server-exp3/thpool.h
Normal 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
BIN
server-exp3/togglec
Executable file
Binary file not shown.
30
server-exp3/togglec.c
Normal file
30
server-exp3/togglec.c
Normal 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
6
server-exp3/togglecm
Normal 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
BIN
server-exp3/togglest_pool
Executable file
Binary file not shown.
113
server-exp3/togglest_pool.c
Normal file
113
server-exp3/togglest_pool.c
Normal 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
2
server-exp3/urls
Normal 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
BIN
server-exp3/webclient
Executable file
Binary file not shown.
55
server-exp3/webclient.c
Normal file
55
server-exp3/webclient.c
Normal 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
BIN
server-exp3/webserver
Executable file
Binary file not shown.
343
server-exp3/webserver.c
Normal file
343
server-exp3/webserver.c
Normal 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
92
server-exp3/webserver.log
Normal 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
BIN
server-exp3/webserver_pool
Executable file
Binary file not shown.
374
server-exp3/webserver_pool.c
Normal file
374
server-exp3/webserver_pool.c
Normal file
@@ -0,0 +1,374 @@
|
||||
#include <arpa/inet.h> // 提供网络地址转换函数
|
||||
#include <errno.h> // 定义错误码
|
||||
#include <fcntl.h> // 文件操作相关函数
|
||||
#include <netinet/in.h> // 定义网络协议的结构体和函数
|
||||
#include <signal.h> // 提供信号处理函数
|
||||
#include <stdio.h> // 标准输入输出
|
||||
#include <stdlib.h> // 标准库函数,如exit()
|
||||
#include <string.h> // 字符串操作函数
|
||||
#include <sys/socket.h> // 套接字相关函数
|
||||
#include <sys/time.h> // 提供 gettimeofday 函数
|
||||
#include <sys/types.h> // 定义数据类型
|
||||
#include <time.h> // 时间相关函数
|
||||
#include <unistd.h> // 提供POSIX API,如read()、write()
|
||||
#include "thpool.h"
|
||||
|
||||
|
||||
#define VERSION 23 // 服务器版本号
|
||||
#define BUFSIZE 8096 // 缓冲区大小
|
||||
#define ERROR 42 // 错误日志类型
|
||||
#define LOG 44 // 普通日志类型
|
||||
#define FORBIDDEN 403 // HTTP状态码:禁止访问
|
||||
#define NOTFOUND 404 // HTTP状态码:未找到资源
|
||||
#define NTHREADS 4
|
||||
#define SBUFSIZE 16
|
||||
|
||||
#ifndef SIGCLD
|
||||
#define SIGCLD SIGCHLD // 为兼容不同系统定义信号别名
|
||||
#endif
|
||||
|
||||
// 支持的文件扩展名及其对应的MIME类型
|
||||
struct {
|
||||
char *ext; // 文件扩展名
|
||||
char *filetype; // MIME类型
|
||||
} extensions[] = {
|
||||
{"gif", "image/gif"},
|
||||
{"jpg", "image/jpg"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"png", "image/png"},
|
||||
{"ico", "image/ico"},
|
||||
{"zip", "image/zip"},
|
||||
{"gz", "image/gz"},
|
||||
{"tar", "image/tar"},
|
||||
{"htm", "text/html"},
|
||||
{"html", "text/html"},
|
||||
{0, 0} // 结束标志
|
||||
};
|
||||
|
||||
typedef struct _param {
|
||||
int hit;
|
||||
int socketfd;
|
||||
} param;
|
||||
|
||||
/* Ctrl+C信号处理函数 */
|
||||
void ctrlc_handler(int sig)
|
||||
{
|
||||
printf("您按下了Ctrl+C终止了Web服务器\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// 计算两个时间点之间的时间差(以毫秒为单位)
|
||||
long calculate_time_diff(struct timespec start, struct timespec end) {
|
||||
long diff_sec = end.tv_sec - start.tv_sec; // 秒部分差值
|
||||
long diff_nsec = end.tv_nsec - start.tv_nsec; // 纳秒部分差值
|
||||
return diff_sec * 1000 + diff_nsec / 1000000.0; // 转换为毫秒
|
||||
}
|
||||
|
||||
void logger(int type, const char *s1, const char *s2, int socket_fd) {
|
||||
int fd;
|
||||
char logbuffer[BUFSIZE * 2];
|
||||
char timebuffer[64]; // 用于存储格式化时间字符串
|
||||
|
||||
// 获取当前时间(秒和微秒)
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
// 将秒部分格式化为字符串(年月日 时分秒)
|
||||
struct tm *tm_info = localtime(&tv.tv_sec);
|
||||
strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
// 添加毫秒部分到时间字符串
|
||||
char full_timebuffer[256];
|
||||
snprintf(full_timebuffer, sizeof(full_timebuffer), "[%s.%03ld]", timebuffer, tv.tv_usec / 1000);
|
||||
|
||||
// 根据日志类型生成日志内容
|
||||
switch (type) {
|
||||
case ERROR: // 错误日志
|
||||
snprintf(logbuffer, sizeof(logbuffer), "%s [ERROR] %s:%s Errno=%d exiting pid=%d",
|
||||
full_timebuffer, s1, s2, errno, getpid());
|
||||
break;
|
||||
case FORBIDDEN: // 403 禁止访问
|
||||
if (write(socket_fd,
|
||||
"HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: "
|
||||
"close\nContent-Type: text/html\n\n"
|
||||
"<html><head>\n<title>403 "
|
||||
"Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n"
|
||||
"The requested URL, file type or operation is not allowed on this simple "
|
||||
"static file webserver.\n"
|
||||
"</body></html>\n",
|
||||
271) < 0) {
|
||||
perror("Failed to send 403 Forbidden response");
|
||||
}
|
||||
snprintf(logbuffer, sizeof(logbuffer), "%s [FORBIDDEN] %s:%s", full_timebuffer, s1, s2);
|
||||
break;
|
||||
case NOTFOUND: // 404 未找到
|
||||
if (write(socket_fd,
|
||||
"HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: "
|
||||
"close\nContent-Type: text/html\n\n"
|
||||
"<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not "
|
||||
"Found</h1>\n"
|
||||
"The requested URL was not found on this server.\n</body></html>\n",
|
||||
224) < 0) {
|
||||
perror("Failed to send 404 Not Found response");
|
||||
}
|
||||
snprintf(logbuffer, sizeof(logbuffer), "%s [NOT FOUND] %s:%s", full_timebuffer, s1, s2);
|
||||
break;
|
||||
case LOG: // 普通日志
|
||||
snprintf(logbuffer, sizeof(logbuffer), "%s [INFO] %s:%s:%d", full_timebuffer, s1, s2,
|
||||
socket_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
// 打开日志文件并写入日志内容
|
||||
#ifdef DEBUG
|
||||
if ((fd = open("webserver.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
|
||||
if (write(fd, logbuffer, strlen(logbuffer)) < 0) {
|
||||
perror("Failed to write log to file");
|
||||
}
|
||||
if (write(fd, "\n", 1) < 0) {
|
||||
perror("Failed to write newline to log file");
|
||||
}
|
||||
close(fd);
|
||||
} else {
|
||||
perror("Failed to open log file");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// 处理HTTP请求的函数
|
||||
void web(void *arg) {
|
||||
int j, file_fd, buflen;
|
||||
long i, ret, len;
|
||||
char *fstr;
|
||||
char buffer[BUFSIZE + 1]; // 改为线程局部栈缓冲区,避免多线程共享同一缓冲区造成竞争
|
||||
param* datap=(param *) arg;
|
||||
int fd = datap->socketfd;
|
||||
int hit = datap->hit;
|
||||
|
||||
// 定义计时变量
|
||||
struct timespec start, end;
|
||||
long duration;
|
||||
logger(LOG, "Process new web request", "", fd);
|
||||
|
||||
// 记录开始时间(读取请求)
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
ret = read(fd, buffer, BUFSIZE); // 读取客户端请求
|
||||
if (ret <= 0) { // 读取失败
|
||||
logger(FORBIDDEN, "Failed to read browser request", "", fd);
|
||||
return;
|
||||
}
|
||||
buffer[ret]='\0';
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("第%d个客户请求:%s\n",hit,buffer);
|
||||
#endif
|
||||
|
||||
// 记录结束时间并计算耗时
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
duration = calculate_time_diff(start, end);
|
||||
logger(LOG, "Time taken for request reading", "", duration);
|
||||
|
||||
// 清除换行符和回车符
|
||||
for (i = 0; i < ret; i++) {
|
||||
if (buffer[i] == '\r' || buffer[i] == '\n') buffer[i] = '*';
|
||||
}
|
||||
|
||||
// 记录开始时间(解析请求)
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
if (strncmp(buffer, "GET ", 4)) { // 检查是否为GET请求
|
||||
logger(FORBIDDEN, "Only simple GET operation supported", buffer, fd);
|
||||
return;
|
||||
}
|
||||
logger(LOG, "request", buffer, hit);
|
||||
|
||||
for (i = 4; i < BUFSIZE; i++) { // 截取文件路径
|
||||
if (buffer[i] == ' ') {
|
||||
buffer[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
duration = calculate_time_diff(start, end);
|
||||
logger(LOG, "Time taken for request parsing", "", duration);
|
||||
|
||||
// 检查路径是否合法
|
||||
for (j = 0; j < i - 1; j++) {
|
||||
if (buffer[j] == '.' && buffer[j + 1] == '.') {
|
||||
logger(FORBIDDEN, "Parent directory (..) path names not supported", buffer, fd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认返回 index.html
|
||||
if (!strncmp(&buffer[0], "GET /\0", 6)) strcpy(buffer, "GET /index.html");
|
||||
|
||||
// 文件类型解析
|
||||
buflen = strlen(buffer);
|
||||
fstr = 0;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
for (i = 0; extensions[i].ext != 0; i++) {
|
||||
len = strlen(extensions[i].ext);
|
||||
if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) {
|
||||
fstr = extensions[i].filetype;
|
||||
break;
|
||||
}
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
duration = calculate_time_diff(start, end);
|
||||
logger(LOG, "Time taken for file type detection", "", duration);
|
||||
|
||||
if (fstr == 0) {
|
||||
logger(FORBIDDEN, "File extension type not supported", buffer, fd);
|
||||
return;
|
||||
}
|
||||
|
||||
// 文件打开
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
if ((file_fd = open(&buffer[5], O_RDONLY)) == -1) {
|
||||
logger(NOTFOUND, "Failed to open file", &buffer[5], fd);
|
||||
return;
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
duration = calculate_time_diff(start, end);
|
||||
logger(LOG, "Time taken for file opening", "", duration);
|
||||
|
||||
// 文件读取与传输
|
||||
logger(LOG, "SEND", &buffer[5], hit);
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
len = (long)lseek(file_fd, 0, SEEK_END);
|
||||
lseek(file_fd, 0, SEEK_SET);
|
||||
sprintf(buffer,
|
||||
"HTTP/1.1 200 OK\nServer: nweb/%d.0\nContent-Length: %ld\nConnection: "
|
||||
"close\nContent-Type: %s\n\n",
|
||||
VERSION, len, fstr);
|
||||
|
||||
logger(LOG, "Header", buffer, hit);
|
||||
if (write(fd, buffer, strlen(buffer)) < 0) {
|
||||
logger(ERROR, "Failed to write HTTP header", "", fd);
|
||||
close(file_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
// 分块读取文件内容并发送
|
||||
while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) {
|
||||
if (write(fd, buffer, ret) < 0) {
|
||||
logger(ERROR, "Failed to write file content", "", fd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
duration = calculate_time_diff(start, end);
|
||||
logger(LOG, "Time taken for file transfer", "", duration);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("第%d个客户服务器完成\n",hit);
|
||||
#endif
|
||||
|
||||
// 关闭文件和套接字
|
||||
free(arg);
|
||||
close(file_fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 主函数,设置服务器并监听客户端连接
|
||||
int main(int argc, char **argv) {
|
||||
int i, port, listenfd, socketfd, hit;
|
||||
socklen_t length;
|
||||
static struct sockaddr_in cli_addr; // 客户端地址
|
||||
static struct sockaddr_in serv_addr; // 服务器地址
|
||||
|
||||
// 检查命令行参数是否正确
|
||||
if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
|
||||
(void)printf(
|
||||
"hint: webserver Port-Number Top-Directory\t\tversion %d\n\n"
|
||||
"\twebserver is a small and very safe mini web server\n"
|
||||
"\twebserver only servers out file/web pages with extensions named below\n"
|
||||
"\t and only from the named directory or its sub-directories.\n"
|
||||
"\tThere is no fancy features = safe and secure.\n\n"
|
||||
"\tExample: webserver 8181 /home/oslab &\n\n"
|
||||
"\tOnly Supports:",
|
||||
VERSION);
|
||||
for (i = 0; extensions[i].ext != 0; i++) (void)printf(" %s", extensions[i].ext);
|
||||
|
||||
(void)printf(
|
||||
"\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
|
||||
"\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
|
||||
"\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* 设置Ctrl+C信号处理 */
|
||||
if(signal(SIGINT,ctrlc_handler)==SIG_ERR)
|
||||
(void)printf("错误: 无法设置SIGINT信号处理\n");
|
||||
|
||||
if (!strncmp(argv[2], "/", 2) || !strncmp(argv[2], "/etc", 5) || !strncmp(argv[2], "/bin", 5) ||
|
||||
!strncmp(argv[2], "/lib", 5) || !strncmp(argv[2], "/tmp", 5) ||
|
||||
!strncmp(argv[2], "/usr", 5) || !strncmp(argv[2], "/dev", 5) ||
|
||||
!strncmp(argv[2], "/sbin", 6)) {
|
||||
(void)printf("ERROR: Bad top directory %s, see nweb -?\n", argv[2]);
|
||||
exit(3);
|
||||
}
|
||||
|
||||
// 验证并切换到指定目录
|
||||
if (chdir(argv[2]) == -1) {
|
||||
perror("Failed to change directory");
|
||||
exit(4);
|
||||
}
|
||||
|
||||
port = atoi(argv[1]);
|
||||
if (port < 1 || port > 60000) {
|
||||
fprintf(stderr, "Invalid port number: %d\n", port);
|
||||
exit(1);
|
||||
}
|
||||
// 设置服务器监听套接字
|
||||
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("Socket creation failed");
|
||||
exit(1);
|
||||
} serv_addr.sin_family = AF_INET;
|
||||
|
||||
/* Eliminates "Address already in use" error from bind. */
|
||||
int optval=1;
|
||||
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *)&optval , sizeof(int)) < 0)
|
||||
return -1;
|
||||
|
||||
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
serv_addr.sin_port = htons(port);
|
||||
if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
|
||||
perror("Bind failed");
|
||||
exit(1);
|
||||
}
|
||||
if (listen(listenfd, 64) < 0) {
|
||||
perror("Listen failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("start main\n");
|
||||
// 初始化线程池
|
||||
thpool_* pool = thpool_init(NTHREADS);
|
||||
|
||||
// 接受客户端连接
|
||||
for (hit = 1;; hit++) {
|
||||
length = sizeof(cli_addr);
|
||||
if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) {
|
||||
logger(ERROR, "system call", "accept", 0);
|
||||
}
|
||||
|
||||
param *new_param = (param*) malloc(sizeof(param));
|
||||
new_param->hit = hit;
|
||||
new_param->socketfd=socketfd;
|
||||
|
||||
thpool_add_work(pool,web, (void*)new_param);
|
||||
}
|
||||
|
||||
// 等待线程池中的所有任务执行完毕
|
||||
thpool_wait(pool);
|
||||
|
||||
// 销毁线程池
|
||||
thpool_destroy(pool);
|
||||
logger(LOG, "webserver exit noramlly", "", 0);
|
||||
return 0;
|
||||
}
|
||||
BIN
server-exp3/实验3任务报告.docx
Normal file
BIN
server-exp3/实验3任务报告.docx
Normal file
Binary file not shown.
3
server-exp3/实验3文件清单.txt
Normal file
3
server-exp3/实验3文件清单.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
taskline.c、taskline.h: 预线程服务器任务管理程序
|
||||
pool.c、pool.h: 线程池服务器的线程管理和任务管理程序
|
||||
urls: 记录运行http_load执行时,访问哪些网址
|
||||
Reference in New Issue
Block a user