0000
This commit is contained in:
Binary file not shown.
+2
-2
@@ -1,2 +1,2 @@
|
||||
http://127.0.0.1:9999/index.html
|
||||
http://127.0.0.1:9999/test.html
|
||||
http://127.0.0.1:8088/index.html
|
||||
http://127.0.0.1:8088/test.html
|
||||
|
||||
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
@@ -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_ */
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user