/** * cache.c — LRU 缓存实现 * * 实现细节: * 1. 使用 DJB2 哈希算法将 URL 映射到桶索引 * 2. 双向链表 (head↔...↔tail) 维护访问顺序: * - head 方向是最近使用 (MRU) * - tail 方向是最久未使用 (LRU) * 3. 查找命中时,将节点移动到链表头部 * 4. 插入满时,淘汰链表尾部的 LRU 节点 * 5. 线程安全: 所有操作使用 pthread_mutex 保护 */ #include #include #include #include #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); }