如何在C语言中过滤同一文件的重复inotify事件?
解决inotify重复事件合并与临时文件过滤问题
针对开发类Dropbox程序时遇到的inotify重复事件触发问题,核心解决思路是给同一文件的连续事件设置短时间窗口,合并重复通知,只保留最关键的事件,同时完善临时文件过滤逻辑,避免误过滤有效操作。
具体实现方案
- 用缓存结构记录最近处理的文件事件,设置200ms合并窗口,窗口内同一文件的事件仅保留优先级最高的(优先级顺序:
IN_CLOSE_WRITE>IN_MOVED_TO>IN_CREATE>IN_MOVED_FROM) - 扩展临时文件过滤规则,覆盖更多常见临时文件类型
- 通过短延迟等待+事件缓存的方式实现事件合并,避免重复触发上传操作
修改后的代码
#include <stdio.h> #include <sys/inotify.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <time.h> #define EVENT_BUF_LEN 4096 #define EVENT_SIZE sizeof(struct inotify_event) #define MAX_CACHED_FILES 64 #define MERGE_DELAY_MS 200 // 事件合并窗口,单位毫秒 // 缓存最近的文件事件,用于去重合并 typedef struct { char filename[256]; int highest_mask; time_t last_event_time; } EventCache; EventCache event_cache[MAX_CACHED_FILES]; // 在缓存中查找指定文件,返回索引,找不到返回-1 int find_in_cache(const char *filename) { for (int i = 0; i < MAX_CACHED_FILES; i++) { if (strcmp(event_cache[i].filename, filename) == 0) { return i; } } return -1; } // 更新缓存中的事件优先级 void update_cache(const char *filename, int mask) { int idx = find_in_cache(filename); if (idx == -1) { // 寻找空位置插入新缓存项 for (int i = 0; i < MAX_CACHED_FILES; i++) { if (event_cache[i].filename[0] == '\0') { strncpy(event_cache[i].filename, filename, sizeof(event_cache[i].filename)-1); event_cache[i].highest_mask = mask; event_cache[i].last_event_time = time(NULL); return; } } // 缓存满时替换最早的记录 int oldest_idx = 0; for (int i = 1; i < MAX_CACHED_FILES; i++) { if (event_cache[i].last_event_time < event_cache[oldest_idx].last_event_time) { oldest_idx = i; } } strncpy(event_cache[oldest_idx].filename, filename, sizeof(event_cache[oldest_idx].filename)-1); event_cache[oldest_idx].highest_mask = mask; event_cache[oldest_idx].last_event_time = time(NULL); return; } // 按优先级更新事件类型 if (mask & IN_CLOSE_WRITE) { event_cache[idx].highest_mask = IN_CLOSE_WRITE; } else if (mask & IN_MOVED_TO && !(event_cache[idx].highest_mask & IN_CLOSE_WRITE)) { event_cache[idx].highest_mask = IN_MOVED_TO; } else if (mask & IN_CREATE && !(event_cache[idx].highest_mask & (IN_CLOSE_WRITE | IN_MOVED_TO))) { event_cache[idx].highest_mask = IN_CREATE; } event_cache[idx].last_event_time = time(NULL); } // 处理缓存中超过合并窗口的事件,输出最终通知 void process_cache() { time_t now = time(NULL); for (int i = 0; i < MAX_CACHED_FILES; i++) { if (event_cache[i].filename[0] == '\0') continue; // 超过延迟窗口则处理事件 if (difftime(now, event_cache[i].last_event_time) * 1000 >= MERGE_DELAY_MS) { char command[256]; if (event_cache[i].highest_mask & IN_CLOSE_WRITE) { strcpy(command, "IN_CLOSE_WRITE "); } else if (event_cache[i].highest_mask & IN_MOVED_TO) { strcpy(command, "IN_MOVED_TO "); } else if (event_cache[i].highest_mask & IN_CREATE) { strcpy(command, "IN_CREATE "); } else if (event_cache[i].highest_mask & IN_MOVED_FROM) { strcpy(command, "IN_MOVED_FROM "); } else { strcpy(command, "UNKNOWN_EVENT "); } strcat(command, event_cache[i].filename); printf("%s\n", command); // 清空当前缓存项 event_cache[i].filename[0] = '\0'; event_cache[i].highest_mask = 0; event_cache[i].last_event_time = 0; } } } // 判断是否为临时文件 int is_temp_file(const char *filename) { // 过滤指定的.gout后缀 if (strstr(filename, ".gout") != NULL) return 1; // 扩展过滤其他常见临时文件 if (filename[strlen(filename)-1] == '~') return 1; if (strstr(filename, ".swp") != NULL) return 1; return 0; } void getNotified(char *pathname) { int fd = inotify_init(); int wd = inotify_add_watch(fd, pathname, IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM); // 初始化事件缓存 memset(event_cache, 0, sizeof(event_cache)); while (1) { char buffer[EVENT_BUF_LEN] __attribute__((aligned(__alignof__(struct inotify_event)))); int length = read(fd, buffer, EVENT_BUF_LEN); int i = 0; while (i < length) { struct inotify_event *event = (struct inotify_event *)&buffer[i]; if (event->len == 0) { i += EVENT_SIZE; continue; } // 过滤临时文件 if (is_temp_file(event->name)) { i += EVENT_SIZE + event->len; continue; } // 更新事件缓存 update_cache(event->name, event->mask); i += EVENT_SIZE + event->len; } // 处理缓存中的事件 process_cache(); // 短睡眠让后续事件有机会进入缓存合并 usleep(MERGE_DELAY_MS * 1000); } inotify_rm_watch(fd, wd); close(fd); } int main(void) { getNotified("."); return 0; }
关键逻辑说明
- 事件合并:通过
EventCache记录文件的最近事件,200ms窗口内同一文件的多次事件会被合并,仅保留最能代表操作完成的高优先级事件(比如修改文件时的重复事件会合并为IN_CLOSE_WRITE通知) - 临时文件过滤:新增了对
~结尾、.swp后缀等常见临时文件的过滤,可根据实际需求扩展规则 - 缓存管理:缓存满时自动替换最早的事件记录,避免内存溢出
内容的提问来源于stack exchange,提问作者NeoFahrenheit
相关产品推荐
相关产品推荐

