0000
This commit is contained in:
311
server-exp4/cache.c
Normal file
311
server-exp4/cache.c
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* cache.c — LRU 缓存实现
|
||||
*
|
||||
* 实现细节:
|
||||
* 1. 使用 DJB2 哈希算法将 URL 映射到桶索引
|
||||
* 2. 双向链表 (head↔...↔tail) 维护访问顺序:
|
||||
* - head 方向是最近使用 (MRU)
|
||||
* - tail 方向是最久未使用 (LRU)
|
||||
* 3. 查找命中时,将节点移动到链表头部
|
||||
* 4. 插入满时,淘汰链表尾部的 LRU 节点
|
||||
* 5. 线程安全: 所有操作使用 pthread_mutex 保护
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include "cache.h"
|
||||
|
||||
/* ======================== 内部常量 ======================== */
|
||||
|
||||
#define DEFAULT_NUM_BUCKETS 101 /* 哈希桶数量 (质数,减少冲突) */
|
||||
#define MAX_CACHE_SIZE 1024 /* 默认最大条目数 */
|
||||
|
||||
/* ======================== 内部辅助函数 ======================== */
|
||||
|
||||
/**
|
||||
* hash_url — DJB2 哈希算法
|
||||
* 该算法简单高效,分布均匀,适合字符串键
|
||||
*/
|
||||
static unsigned long hash_url(const char *url)
|
||||
{
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
while ((c = *url++))
|
||||
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* list_remove — 从双向链表中移除节点 (不释放内存)
|
||||
*/
|
||||
static void list_remove(cache_t *cache, cache_entry_t *entry)
|
||||
{
|
||||
if (entry->prev)
|
||||
entry->prev->next = entry->next;
|
||||
else
|
||||
cache->head = entry->next; /* 移除的是头节点 */
|
||||
|
||||
if (entry->next)
|
||||
entry->next->prev = entry->prev;
|
||||
else
|
||||
cache->tail = entry->prev; /* 移除的是尾节点 */
|
||||
}
|
||||
|
||||
/**
|
||||
* list_add_head — 将节点添加到链表头部 (MRU 位置)
|
||||
*/
|
||||
static void list_add_head(cache_t *cache, cache_entry_t *entry)
|
||||
{
|
||||
entry->prev = NULL;
|
||||
entry->next = cache->head;
|
||||
if (cache->head)
|
||||
cache->head->prev = entry;
|
||||
else
|
||||
cache->tail = entry; /* 链表为空,也是尾节点 */
|
||||
cache->head = entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* evict_lru — 淘汰 LRU 节点 (链表尾部)
|
||||
* 从哈希表和链表中移除,释放内存
|
||||
*/
|
||||
static void evict_lru(cache_t *cache)
|
||||
{
|
||||
if (cache->tail == NULL)
|
||||
return;
|
||||
|
||||
cache_entry_t *victim = cache->tail;
|
||||
|
||||
/* 从链表中移除 */
|
||||
list_remove(cache, victim);
|
||||
|
||||
/* 从哈希桶中移除 */
|
||||
unsigned long h = hash_url(victim->url) % cache->num_buckets;
|
||||
cache_entry_t **pp = &cache->buckets[h];
|
||||
while (*pp) {
|
||||
if (*pp == victim) {
|
||||
*pp = victim->hnext;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->hnext;
|
||||
}
|
||||
|
||||
/* 释放内存 */
|
||||
free(victim->url);
|
||||
free(victim->data);
|
||||
free(victim);
|
||||
|
||||
cache->cur_size--;
|
||||
cache->evictions++;
|
||||
}
|
||||
|
||||
/* ======================== 公开 API 实现 ======================== */
|
||||
|
||||
/**
|
||||
* cache_init — 初始化 LRU 缓存
|
||||
*/
|
||||
cache_t *cache_init(int max_size)
|
||||
{
|
||||
cache_t *cache = (cache_t *)calloc(1, sizeof(cache_t));
|
||||
if (cache == NULL) {
|
||||
perror("cache_init: calloc failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cache->num_buckets = DEFAULT_NUM_BUCKETS;
|
||||
cache->max_size = (max_size > 0 && max_size <= MAX_CACHE_SIZE)
|
||||
? max_size : MAX_CACHE_SIZE;
|
||||
cache->cur_size = 0;
|
||||
cache->head = NULL;
|
||||
cache->tail = NULL;
|
||||
cache->hits = 0;
|
||||
cache->misses = 0;
|
||||
cache->evictions = 0;
|
||||
cache->bytes_served = 0;
|
||||
|
||||
cache->buckets = (cache_entry_t **)calloc(cache->num_buckets,
|
||||
sizeof(cache_entry_t *));
|
||||
if (cache->buckets == NULL) {
|
||||
perror("cache_init: calloc buckets failed");
|
||||
free(cache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&cache->lock, NULL) != 0) {
|
||||
perror("cache_init: mutex_init failed");
|
||||
free(cache->buckets);
|
||||
free(cache);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf("[Cache] 初始化完成: 最大条目=%d, 桶数量=%d\n",
|
||||
cache->max_size, cache->num_buckets);
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* cache_lookup — 在缓存中查找 URL (O(1) 平均)
|
||||
*
|
||||
* 流程:
|
||||
* 1. 计算哈希值,定位桶
|
||||
* 2. 遍历桶内冲突链查找匹配 URL
|
||||
* 3. 命中: 将条目移到链表头部 (标记为最近使用),拷贝数据
|
||||
* 4. 未命中: 返回 0
|
||||
*/
|
||||
int cache_lookup(cache_t *cache, const char *url,
|
||||
char **data, long *size, char *filetype)
|
||||
{
|
||||
if (cache == NULL || url == NULL)
|
||||
return 0;
|
||||
|
||||
pthread_mutex_lock(&cache->lock);
|
||||
|
||||
unsigned long h = hash_url(url) % cache->num_buckets;
|
||||
cache_entry_t *entry = cache->buckets[h];
|
||||
|
||||
while (entry) {
|
||||
if (strcmp(entry->url, url) == 0) {
|
||||
/* 缓存命中: 移动到链表头部 */
|
||||
list_remove(cache, entry);
|
||||
list_add_head(cache, entry);
|
||||
|
||||
/* 更新访问时间 */
|
||||
clock_gettime(CLOCK_MONOTONIC, &entry->timestamp);
|
||||
|
||||
/* 拷贝数据给调用者 */
|
||||
*size = entry->size;
|
||||
strcpy(filetype, entry->filetype);
|
||||
*data = (char *)malloc(entry->size);
|
||||
if (*data)
|
||||
memcpy(*data, entry->data, entry->size);
|
||||
|
||||
cache->hits++;
|
||||
cache->bytes_served += entry->size;
|
||||
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return 1;
|
||||
}
|
||||
entry = entry->hnext;
|
||||
}
|
||||
|
||||
/* 缓存未命中 */
|
||||
cache->misses++;
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cache_insert — 向缓存插入新条目
|
||||
*
|
||||
* 流程:
|
||||
* 1. 若缓存已满,先淘汰 LRU 条目
|
||||
* 2. 创建新条目,拷贝数据
|
||||
* 3. 添加到哈希表和链表头部
|
||||
*/
|
||||
int cache_insert(cache_t *cache, const char *url,
|
||||
const char *data, long size, const char *filetype)
|
||||
{
|
||||
if (cache == NULL || url == NULL || data == NULL || size <= 0)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_lock(&cache->lock);
|
||||
|
||||
/* 检查是否已存在 (更新) */
|
||||
unsigned long h = hash_url(url) % cache->num_buckets;
|
||||
cache_entry_t *entry = cache->buckets[h];
|
||||
while (entry) {
|
||||
if (strcmp(entry->url, url) == 0) {
|
||||
/* 已存在: 更新数据和位置 */
|
||||
free(entry->data);
|
||||
entry->data = (char *)malloc(size);
|
||||
if (entry->data == NULL) {
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return -1;
|
||||
}
|
||||
memcpy(entry->data, data, size);
|
||||
entry->size = size;
|
||||
strcpy(entry->filetype, filetype);
|
||||
clock_gettime(CLOCK_MONOTONIC, &entry->timestamp);
|
||||
|
||||
/* 移动到头部 */
|
||||
list_remove(cache, entry);
|
||||
list_add_head(cache, entry);
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return 0;
|
||||
}
|
||||
entry = entry->hnext;
|
||||
}
|
||||
|
||||
/* 缓存已满,淘汰 LRU */
|
||||
while (cache->cur_size >= cache->max_size)
|
||||
evict_lru(cache);
|
||||
|
||||
/* 创建新条目 */
|
||||
cache_entry_t *new_entry = (cache_entry_t *)calloc(1, sizeof(cache_entry_t));
|
||||
if (new_entry == NULL) {
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_entry->url = strdup(url);
|
||||
new_entry->data = (char *)malloc(size);
|
||||
if (new_entry->url == NULL || new_entry->data == NULL) {
|
||||
free(new_entry->url);
|
||||
free(new_entry->data);
|
||||
free(new_entry);
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(new_entry->data, data, size);
|
||||
new_entry->size = size;
|
||||
strcpy(new_entry->filetype, filetype);
|
||||
clock_gettime(CLOCK_MONOTONIC, &new_entry->timestamp);
|
||||
|
||||
/* 添加到哈希桶 */
|
||||
new_entry->hnext = cache->buckets[h];
|
||||
cache->buckets[h] = new_entry;
|
||||
|
||||
/* 添加到链表头部 */
|
||||
list_add_head(cache, new_entry);
|
||||
cache->cur_size++;
|
||||
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cache_free — 释放所有缓存资源
|
||||
*/
|
||||
void cache_free(cache_t *cache)
|
||||
{
|
||||
if (cache == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&cache->lock);
|
||||
|
||||
/* 遍历链表释放所有条目 */
|
||||
cache_entry_t *entry = cache->head;
|
||||
while (entry) {
|
||||
cache_entry_t *next = entry->next;
|
||||
free(entry->url);
|
||||
free(entry->data);
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
free(cache->buckets);
|
||||
pthread_mutex_unlock(&cache->lock);
|
||||
pthread_mutex_destroy(&cache->lock);
|
||||
|
||||
printf("[Cache] 释放完成: 命中=%lld 未命中=%lld 淘汰=%lld "
|
||||
"命中率=%.1f%% 服务字节=%lld\n",
|
||||
cache->hits, cache->misses, cache->evictions,
|
||||
cache->hits + cache->misses > 0
|
||||
? 100.0 * cache->hits / (cache->hits + cache->misses)
|
||||
: 0.0,
|
||||
cache->bytes_served);
|
||||
|
||||
free(cache);
|
||||
}
|
||||
92
server-exp4/cache.h
Normal file
92
server-exp4/cache.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* cache.h — Web服务器 LRU 缓存模块
|
||||
*
|
||||
* 设计思想:
|
||||
* 使用 哈希表 + 双向链表 实现 O(1) 的查找、插入和淘汰操作。
|
||||
* 哈希表提供快速键值查找,双向链表维护 LRU 访问顺序。
|
||||
* 最近被访问的节点移动到链表头部 (MRU位置),
|
||||
* 链表尾部的节点是最久未被访问的 (LRU位置),淘汰时优先移除。
|
||||
*
|
||||
* 数据结构:
|
||||
* cache_entry: 单个缓存条目 (文件名、文件内容、大小、MIME类型、时间戳)
|
||||
* cache_t: 缓存管理器 (哈希表桶数组、链表头尾、统计计数器)
|
||||
*
|
||||
* 主要操作:
|
||||
* cache_init(max_size): 初始化缓存,设置最大条目数
|
||||
* cache_lookup(url, buf, size, type): 查找缓存,命中时拷贝数据并返回 1
|
||||
* cache_insert(url, data, size, type): 插入缓存,必要时淘汰 LRU 条目
|
||||
* cache_free(): 释放所有缓存内存
|
||||
*/
|
||||
|
||||
#ifndef _CACHE_H_
|
||||
#define _CACHE_H_
|
||||
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* 缓存条目 */
|
||||
typedef struct cache_entry {
|
||||
char *url; /* 请求 URL (作为键) */
|
||||
char *data; /* 文件内容 */
|
||||
long size; /* 文件大小 */
|
||||
char filetype[64]; /* MIME 类型 */
|
||||
struct timespec timestamp; /* 最后访问时间 */
|
||||
struct cache_entry *prev; /* 双向链表前驱 */
|
||||
struct cache_entry *next; /* 双向链表后继 */
|
||||
struct cache_entry *hnext; /* 哈希桶内链表 (冲突链) */
|
||||
} cache_entry_t;
|
||||
|
||||
/* 缓存管理器 */
|
||||
typedef struct {
|
||||
cache_entry_t **buckets; /* 哈希桶数组 */
|
||||
int num_buckets; /* 哈希桶数量 */
|
||||
int max_size; /* 最大条目数 */
|
||||
int cur_size; /* 当前条目数 */
|
||||
cache_entry_t *head; /* LRU 链表头 (最近使用) */
|
||||
cache_entry_t *tail; /* LRU 链表尾 (最久未使用) */
|
||||
pthread_mutex_t lock; /* 线程安全锁 */
|
||||
|
||||
/* 统计信息 */
|
||||
long long hits; /* 缓存命中次数 */
|
||||
long long misses; /* 缓存未命中次数 */
|
||||
long long evictions; /* 淘汰次数 */
|
||||
long long bytes_served; /* 从缓存服务的字节数 */
|
||||
} cache_t;
|
||||
|
||||
/**
|
||||
* cache_init — 初始化 LRU 缓存
|
||||
* @max_size: 最大缓存条目数
|
||||
* @return: 指向 cache_t 的指针,失败返回 NULL
|
||||
*/
|
||||
cache_t *cache_init(int max_size);
|
||||
|
||||
/**
|
||||
* cache_lookup — 在缓存中查找 URL
|
||||
* @cache: 缓存管理器指针
|
||||
* @url: 请求的 URL 键
|
||||
* @data: 输出参数,命中时指向缓存数据的拷贝 (调用者需 free)
|
||||
* @size: 输出参数,数据大小
|
||||
* @filetype: 输出参数,MIME 类型
|
||||
* @return: 1=命中, 0=未命中
|
||||
*/
|
||||
int cache_lookup(cache_t *cache, const char *url,
|
||||
char **data, long *size, char *filetype);
|
||||
|
||||
/**
|
||||
* cache_insert — 向缓存插入新条目
|
||||
* @cache: 缓存管理器指针
|
||||
* @url: URL 键
|
||||
* @data: 文件内容 (缓存会拷贝一份)
|
||||
* @size: 文件大小
|
||||
* @filetype: MIME 类型
|
||||
* @return: 0=成功, -1=失败
|
||||
*/
|
||||
int cache_insert(cache_t *cache, const char *url,
|
||||
const char *data, long size, const char *filetype);
|
||||
|
||||
/**
|
||||
* cache_free — 释放所有缓存资源
|
||||
*/
|
||||
void cache_free(cache_t *cache);
|
||||
|
||||
#endif /* _CACHE_H_ */
|
||||
BIN
server-exp4/cgi-bin/add
Executable file
BIN
server-exp4/cgi-bin/add
Executable file
Binary file not shown.
63
server-exp4/cgi-bin/add.c
Normal file
63
server-exp4/cgi-bin/add.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* cgi-bin/add.c — 简单CGI加法计算器
|
||||
*
|
||||
* 用于测试Web服务器的CGI功能
|
||||
* GET: /cgi-bin/add?a=10&b=20
|
||||
* POST: 提交表单到 /cgi-bin/add
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char *query = getenv("QUERY_STRING");
|
||||
char *method = getenv("REQUEST_METHOD");
|
||||
|
||||
printf("Content-Type: text/html; charset=UTF-8\r\n\r\n");
|
||||
printf("<!DOCTYPE html>\n<html><head>\n");
|
||||
printf("<meta charset=\"UTF-8\">\n");
|
||||
printf("<title>CGI Calculator</title>\n");
|
||||
printf("<style>body{font-family:Arial;margin:40px;}"
|
||||
"h1{color:#2a7;}.result{background:#f5f5f5;padding:20px;"
|
||||
"border-radius:10px;font-size:1.5em;text-align:center;}"
|
||||
"a{color:#667eea;}</style>\n");
|
||||
printf("</head><body>\n");
|
||||
printf("<h1>🔧 CGI 动态计算器 (实验4)</h1>\n");
|
||||
|
||||
/* 从 stdin 读取 POST 数据 */
|
||||
char post_data[1024] = "";
|
||||
if (method && strcmp(method, "POST") == 0) {
|
||||
char *len_str = getenv("CONTENT_LENGTH");
|
||||
if (len_str) {
|
||||
int len = atoi(len_str);
|
||||
if (len > 0 && len < (int)sizeof(post_data) - 1) {
|
||||
fread(post_data, 1, len, stdin);
|
||||
post_data[len] = '\0';
|
||||
}
|
||||
}
|
||||
query = post_data;
|
||||
}
|
||||
|
||||
/* 解析参数 a 和 b */
|
||||
int a = 0, b = 0;
|
||||
if (query) {
|
||||
char *pa = strstr(query, "a=");
|
||||
char *pb = strstr(query, "b=");
|
||||
if (pa) a = atoi(pa + 2);
|
||||
if (pb) b = atoi(pb + 2);
|
||||
}
|
||||
|
||||
printf("<div class=\"result\">\n");
|
||||
printf("<p>%d + %d = <strong>%d</strong></p>\n", a, b, a + b);
|
||||
printf("<p>Method: %s | PID: %d</p>\n",
|
||||
method ? method : "UNKNOWN", getpid());
|
||||
printf("</div>\n");
|
||||
|
||||
printf("<p><a href=\"/\">← 返回首页</a></p>\n");
|
||||
printf("<hr><em>Experiment4 CGI v1.0</em>\n");
|
||||
printf("</body></html>\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
170
server-exp4/common.c
Normal file
170
server-exp4/common.c
Normal file
@@ -0,0 +1,170 @@
|
||||
/* $begin common.c */
|
||||
#include "common.h"
|
||||
|
||||
/*********************************************************************
|
||||
* The Rio package - robust I/O functions
|
||||
**********************************************************************/
|
||||
|
||||
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)
|
||||
nread = 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
else if (nread == 0)
|
||||
break;
|
||||
nleft -= nread;
|
||||
bufp += nread;
|
||||
}
|
||||
return (n - nleft);
|
||||
}
|
||||
|
||||
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)
|
||||
nwritten = 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
nleft -= nwritten;
|
||||
bufp += nwritten;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
|
||||
{
|
||||
int cnt;
|
||||
while (rp->rio_cnt <= 0) {
|
||||
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
|
||||
sizeof(rp->rio_buf));
|
||||
if (rp->rio_cnt < 0) {
|
||||
if (errno != EINTR)
|
||||
return -1;
|
||||
}
|
||||
else if (rp->rio_cnt == 0)
|
||||
return 0;
|
||||
else
|
||||
rp->rio_bufptr = rp->rio_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;
|
||||
}
|
||||
|
||||
void rio_readinitb(rio_t *rp, int fd)
|
||||
{
|
||||
rp->rio_fd = fd;
|
||||
rp->rio_cnt = 0;
|
||||
rp->rio_bufptr = rp->rio_buf;
|
||||
}
|
||||
|
||||
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)
|
||||
nread = 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
else if (nread == 0)
|
||||
break;
|
||||
nleft -= nread;
|
||||
bufp += nread;
|
||||
}
|
||||
return (n - nleft);
|
||||
}
|
||||
|
||||
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;
|
||||
else
|
||||
break;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
*bufp = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/********************************
|
||||
* Client/server helper functions
|
||||
********************************/
|
||||
|
||||
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;
|
||||
|
||||
if ((hp = gethostbyname(hostname)) == NULL)
|
||||
return -2;
|
||||
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);
|
||||
|
||||
if (connect(client_sock, (SA *) &serveraddr, sizeof(serveraddr)) < 0)
|
||||
return -1;
|
||||
return client_sock;
|
||||
}
|
||||
|
||||
int open_listen_sock(int port)
|
||||
{
|
||||
int listen_sock, optval=1;
|
||||
struct sockaddr_in serveraddr;
|
||||
|
||||
if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
return -1;
|
||||
|
||||
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *)&optval , sizeof(int)) < 0)
|
||||
return -1;
|
||||
|
||||
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;
|
||||
|
||||
if (listen(listen_sock, LISTENQ) < 0)
|
||||
return -1;
|
||||
return listen_sock;
|
||||
}
|
||||
66
server-exp4/common.h
Normal file
66
server-exp4/common.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/* $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 <ctype.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/epoll.h>
|
||||
#include <sys/timerfd.h>
|
||||
|
||||
/* Default file permissions */
|
||||
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
|
||||
#define DEF_UMASK S_IWGRP|S_IWOTH
|
||||
|
||||
/* Simplifies calls to bind(), connect(), and accept() */
|
||||
typedef struct sockaddr SA;
|
||||
|
||||
/* Persistent state for the robust I/O (Rio) package */
|
||||
#define RIO_BUFSIZE 8192
|
||||
typedef struct {
|
||||
int rio_fd;
|
||||
int rio_cnt;
|
||||
char *rio_bufptr;
|
||||
char rio_buf[RIO_BUFSIZE];
|
||||
} rio_t;
|
||||
|
||||
/* External variables */
|
||||
extern int h_errno;
|
||||
extern char **environ;
|
||||
|
||||
/* Misc constants */
|
||||
#define MAXLINE 8192
|
||||
#define MAXBUF 8192
|
||||
#define LISTENQ 1024
|
||||
|
||||
/* 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__ */
|
||||
BIN
server-exp4/example.jpg
Normal file
BIN
server-exp4/example.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
BIN
server-exp4/favicon.ico
Normal file
BIN
server-exp4/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
191
server-exp4/index.html
Normal file
191
server-exp4/index.html
Normal file
@@ -0,0 +1,191 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>实验4 高性能Web服务器 - 测试页面</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
max-width: 800px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2em;
|
||||
}
|
||||
h2 {
|
||||
color: #555;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.feature-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border: 2px solid #e9ecef;
|
||||
transition: transform 0.2s, border-color 0.2s;
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: #667eea;
|
||||
}
|
||||
.feature-icon { font-size: 2em; margin-bottom: 10px; }
|
||||
.feature-title { font-weight: bold; color: #333; margin-bottom: 5px; }
|
||||
.feature-desc { font-size: 0.9em; color: #666; }
|
||||
.test-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.test-section h3 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
input[type="text"], input[type="email"], textarea {
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
input:focus, textarea:focus {
|
||||
border-color: #667eea;
|
||||
outline: none;
|
||||
}
|
||||
button, .btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
button:hover, .btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102,126,234,0.4);
|
||||
}
|
||||
.link-list { list-style: none; padding: 0; }
|
||||
.link-list li { margin: 8px 0; }
|
||||
.link-list a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.link-list a:hover { text-decoration: underline; }
|
||||
.server-info {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 0.85em;
|
||||
margin-top: 20px;
|
||||
}
|
||||
pre { background: white; padding: 15px; border-radius: 8px; overflow-x: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 实验4 高性能Web服务器</h1>
|
||||
<h2>epoll + 线程池 + LRU缓存 + GET/POST</h2>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">⚡</div>
|
||||
<div class="feature-title">epoll I/O复用</div>
|
||||
<div class="feature-desc">基于epoll的边缘触发模式,高效处理海量并发连接</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🧵</div>
|
||||
<div class="feature-title">线程池</div>
|
||||
<div class="feature-desc">预创建工作线程,避免频繁创建销毁开销</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">💾</div>
|
||||
<div class="feature-title">LRU缓存</div>
|
||||
<div class="feature-desc">哈希表+双向链表,O(1)查找与淘汰</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📡</div>
|
||||
<div class="feature-title">GET/POST</div>
|
||||
<div class="feature-desc">完整支持HTTP GET和POST方法</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>📋 功能测试</h3>
|
||||
<ul class="link-list">
|
||||
<li><a href="/test.html">📄 静态页面测试 (GET)</a></li>
|
||||
<li><a href="/example.jpg">🖼️ 图片访问测试 (JPEG)</a></li>
|
||||
<li><a href="/nonexistent.html">❌ 404错误页面测试</a></li>
|
||||
<li><a href="/cgi-bin/add?a=10&b=20">🔧 CGI动态内容测试</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>📝 POST 方法测试</h3>
|
||||
<form action="/submit" method="POST">
|
||||
<label for="name">姓名:</label>
|
||||
<input type="text" id="name" name="name" placeholder="输入您的姓名" required>
|
||||
<label for="email">邮箱:</label>
|
||||
<input type="email" id="email" name="email" placeholder="输入您的邮箱" required>
|
||||
<label for="message">留言:</label>
|
||||
<textarea id="message" name="message" rows="3" placeholder="输入您的留言..."></textarea>
|
||||
<button type="submit">✉️ 提交 POST 请求</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>📊 缓存测试</h3>
|
||||
<p style="color:#666;margin-bottom:10px;">
|
||||
多次访问同一资源观察 X-Cache 响应头: 首次 MISS,后续 HIT
|
||||
</p>
|
||||
<pre># 测试缓存命中
|
||||
curl -I http://localhost:8088/index.html
|
||||
# 第一次: X-Cache: MISS
|
||||
# 第二次: X-Cache: HIT (从缓存返回)
|
||||
|
||||
# 压力测试
|
||||
ab -n 10000 -c 100 http://localhost:8088/index.html
|
||||
wrk -t4 -c100 -d30s http://localhost:8088/index.html</pre>
|
||||
</div>
|
||||
|
||||
<div class="server-info">
|
||||
<p>Powered by Experiment4-WebServer v40.0 | epoll + Thread Pool + LRU Cache</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
48
server-exp4/test.html
Normal file
48
server-exp4/test.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>静态页面测试 - 实验4</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; background: #f0f4f8; color: #333; }
|
||||
h1 { color: #2a7; }
|
||||
.box { background: white; padding: 20px; border-radius: 10px; margin: 15px 0;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
|
||||
th { background: #667eea; color: white; }
|
||||
a { color: #667eea; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>📄 静态页面测试</h1>
|
||||
<p>本页面用于验证Web服务器的静态资源服务能力(包括缓存功能)。</p>
|
||||
|
||||
<div class="box">
|
||||
<h2>服务器特性对比</h2>
|
||||
<table>
|
||||
<tr><th>版本</th><th>I/O模型</th><th>并发模型</th><th>缓存</th><th>方法</th></tr>
|
||||
<tr><td>实验1</td><td>阻塞I/O</td><td>单进程</td><td>❌</td><td>GET</td></tr>
|
||||
<tr><td>实验2</td><td>阻塞I/O</td><td>多线程/预线程</td><td>❌</td><td>GET</td></tr>
|
||||
<tr><td>实验3</td><td>阻塞I/O</td><td>线程池/流水线</td><td>❌</td><td>GET</td></tr>
|
||||
<tr><td style="color:#2a7;font-weight:bold;">实验4</td>
|
||||
<td style="color:#2a7;">epoll</td>
|
||||
<td style="color:#2a7;">线程池</td>
|
||||
<td style="color:#2a7;">✅ LRU</td>
|
||||
<td style="color:#2a7;">GET+POST</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2>实验4 新增功能</h2>
|
||||
<ul>
|
||||
<li><strong>epoll I/O多路复用:</strong> 主线程使用epoll监控所有连接,边缘触发模式</li>
|
||||
<li><strong>LRU缓存:</strong> 哈希表 + 双向链表,O(1)查找和淘汰</li>
|
||||
<li><strong>POST方法:</strong> 支持表单提交和CGI转发</li>
|
||||
<li><strong>缓存命中指示:</strong> 响应头 X-Cache: HIT/MISS</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p><a href="/">← 返回首页</a></p>
|
||||
</body>
|
||||
</html>
|
||||
478
server-exp4/thpool.c
Normal file
478
server-exp4/thpool.c
Normal file
@@ -0,0 +1,478 @@
|
||||
/* ********************************
|
||||
* 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;
|
||||
|
||||
/* Thread-local worker ID,供外部模块统计每个线程的请求处理数 */
|
||||
__thread int thpool_worker_id = -1;
|
||||
|
||||
/* ========================== 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;
|
||||
thpool_worker_id = thread_p->id; /* 设置 thread-local ID */
|
||||
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-exp4/thpool.h
Normal file
222
server-exp4/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
|
||||
16
server-exp4/urls.txt
Normal file
16
server-exp4/urls.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=1&b=2
|
||||
http://localhost:8088/cgi-bin/add?a=10&b=20
|
||||
http://localhost:8088/cgi-bin/add?a=100&b=200
|
||||
http://localhost:8088/cgi-bin/add?a=5&b=15
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=7&b=8
|
||||
http://localhost:8088/cgi-bin/add?a=99&b=1
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=50&b=50
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=33&b=67
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=6&b=14
|
||||
http://localhost:8088/index.html
|
||||
http://localhost:8088/cgi-bin/add?a=42&b=58
|
||||
BIN
server-exp4/webserver
Executable file
BIN
server-exp4/webserver
Executable file
Binary file not shown.
1149
server-exp4/webserver.c
Normal file
1149
server-exp4/webserver.c
Normal file
File diff suppressed because it is too large
Load Diff
1200
server-exp4/webserver.log
Normal file
1200
server-exp4/webserver.log
Normal file
File diff suppressed because it is too large
Load Diff
BIN
server-exp4/实验4 课程设计任务书(缓存和epoll).docx
Normal file
BIN
server-exp4/实验4 课程设计任务书(缓存和epoll).docx
Normal file
Binary file not shown.
BIN
server-exp4/实验4 课程设计报告-模板(1).docx
Normal file
BIN
server-exp4/实验4 课程设计报告-模板(1).docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user