You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

关键逻辑说明

  1. 事件合并:通过EventCache记录文件的最近事件,200ms窗口内同一文件的多次事件会被合并,仅保留最能代表操作完成的高优先级事件(比如修改文件时的重复事件会合并为IN_CLOSE_WRITE通知)
  2. 临时文件过滤:新增了对~结尾、.swp后缀等常见临时文件的过滤,可根据实际需求扩展规则
  3. 缓存管理:缓存满时自动替换最早的事件记录,避免内存溢出

内容的提问来源于stack exchange,提问作者NeoFahrenheit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 09:30:57