This commit is contained in:
2026-07-18 16:39:01 +08:00
parent 25f7ef1967
commit ce5e55a2c1
24 changed files with 5015 additions and 2 deletions

311
server-exp4/cache.c Normal file
View 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);
}