监听文件变化的实现

Linux下inotify特性:

inotify是内核一个特性, 可以用来监控目录, 文件的读写等事件. 当监控目标是目录时, inotify除了会监控目录本身, 还会监控目
录中的文件. inotify的监控功能由如下几个系统调用组成: inotify_init1, inotify_add_watch, inotify_rm_watch,
read 和 close.

inotify的主要操作基于inotify_init1返回的 inotify 文件描述符, 该描述符的作用类似于 epoll 的 epoll_fd. inotify
在监控目录的时候, 不支持对目录的地柜监控, 即只能监控一层目录, 如果需要地柜监控, 就需要将这些目录通过 inotify_add_watch
添加进来.

核心: inotify系统调用产生的 fd 可以使用 epoll 进行去监听(类似于网络fd). 对于 inotify 当中监听的文件的变更都会使得
inotifyfd 就绪, 从而可以从 inotifyfd 当中读取就绪的内容(文件变化的情况)

fsnotify 的工作原理如下:

1. 创建 inotify 文件描述符. (即 SYS_INOTIFY_INIT1 系统调用)2. 创建 pipe 管道. (程序唤醒退出的作用, 只是辅助作用.)3. 创建 epoller. (只是为了高效率呗, 其实 select, polle 也可以的).4. 将 inotifyfd, pipefd[0] 的 EPOLLIN 事件添加到 epoll 当中. (就是 epoll 的添加注册事件呗, 为后续的监听等待准备)5. 新开一个 goroutine, 从 epoll 当中获取就绪的事件(阻塞调用), 如果文件有变化时, 就可以从 inotifyfd 当中读取件内容
InotifyEvent, 里面包含了事件, 文件描述符fd, 变更文件的临时名称. 存储格式:InotifyEvent:
[Wd     int32  // 变化的文件fdMask   uint32 Cookie uint32Len    uint32 // Data 长度Data   []byte // 文件的路径
]注意: 一次性可能存在多个事件发生.如果是 `pipefd[0]` 有事件发生, 那么就是唤醒程序退出.6. 将文件/目录添加到 inotifyfd 监控当中(相对于监听是异步的). 主要监控的事件有: IN_MOVED_TO, IN_CREATE, IN_MOVED_FROM,
IN_ATTRIB, IN_MODIFY, IN_MOVE_SELF, IN_MOVE_SELF, IN_DELETE_SELF.IN_MOVED_TO, IN_CREATE 是新增文件
IN_DELETE_SELF, IN_MODIFY 是删除文件
IN_MODIFY 是修改文件
IN_MOVE_SELF, IN_MOVED_FROM 是文件重命名
IN_ATTRIB 是文件权限
  • 创建 inotify 监听
func NewWatcher() (*Watcher, error) {poller, err := newFdPoller(fd) // epollif err != nil {unix.Close(fd)return nil, err}w := &Watcher{fd:       poller.fd,poller:   poller,watches:  make(map[string]*watch), // 监听的文件/目录paths:    make(map[int]string),    // fd <-> path 映射Events:   make(chan Event), // 事件Errors:   make(chan error), // 错误done:     make(chan struct{}), // 结束的标记doneResp: make(chan struct{}), // 结束唤醒的标记}go w.readEvents()return w, nil
}

// epoll 构建
// epoll { pipefd[0], inotifyfd }, 在这里 inotifyfd 是核心, 文件所有的变动都得通过它来告知. pipefd[0]
// 只是一个辅助, 用于程序唤醒退出的作用.

创建一个 pipe(匿名管道), 会打开两个文件描述符, fd[0] 是读, fd[1] 是写.

// epoll 创建, 添加关注的事件
func newFdPoller() (*fdPoller, error) {// 创建 inotify_fdfd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)if fd == -1 {return nil, errno}var errno errorpoller := new(fdPoller)poller.fd = fdpoller.epfd = -1poller.pipe[0] = -1poller.pipe[1] = -1defer func() {if errno != nil {poller.close()}}()// 匿名管道 pipe, 其中 pipe[0] 是读, pipe[1]是写.errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)if errno != nil {return nil, errno}// epollpoller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)if poller.epfd == -1 {return nil, errno}// 注册 inotifyfd "读" 的事件到 epollevent := unix.EpollEvent{Fd:     int32(poller.fd),Events: unix.EPOLLIN,}errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)// 注册 pipfd[0] "读" 的事件到 epollevent = unix.EpollEvent{Fd:     int32(poller.pipe[0]),Events: unix.EPOLLIN,}errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)return poller, nil
}

// epoll 监听(EPOLLIN 事件)

func (poller *fdPoller) wait() (bool, error) {// 监听 EPOLLIN, 同时 EPOLLHUP, EPOLLERR 是无须监听, 任何时候都会发生// 2*3+1events := make([]unix.EpollEvent, 7)for {n, errno := unix.EpollWait(poller.epfd, events, -1) if n == -1 {if errno == unix.EINTR {continue}return false, errno}if n == 0 {continue}if n > 6 {return false, errors.New("epoll_wait returned more events than I know what to do with")}// n个就绪事件ready := events[:n]var epollhup, epollerr, epollin boolfor _, event := range ready {// inotifyfd, 真正关心的事件if event.Fd == int32(poller.fd) {if event.Events&unix.EPOLLHUP != 0 {epollhup = true}if event.Events&unix.EPOLLERR != 0 {epollerr = true}if event.Events&unix.EPOLLIN != 0 {epollin = true}}// pipefd[0], 一般是错误事件.if event.Fd == int32(poller.pipe[0]) {if event.Events&unix.EPOLLHUP != 0 {// Write pipe descriptor was closed, by us. This means we're closing down the// watcher, and we should wake up.}if event.Events&unix.EPOLLERR != 0 {// If an error is waiting on the pipe file descriptor.// This is an absolute mystery, and should never ever happen.return false, errors.New("Error on the pipe descriptor.")}if event.Events&unix.EPOLLIN != 0 {// 常规的唤醒操作, 唤醒之后需要立即清理写入的缓存(因为写入缓存, 导致唤醒事件的发生).// 从 pipefd[0] 当中读出缓存. 因为 wake() 就是向 pipfd[1] 当中写入了缓存.// 这样做的目的: 当需要 close 的时候, 程序创建调用 wake() 从而使 epoll EpollWait() 快速// 唤醒, 从而程序退出.err := poller.clearWake() if err != nil {return false, err}}}}if epollhup || epollerr || epollin {return true, nil}return false, nil}
}
  • 监听(异步)

// 将文件的变化通过 chan 发送给客户端.

func (w *Watcher) readEvents() {var (buf   [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw eventsn     int                                  // Number of bytes read with read()errno error                                // Syscall errnook    bool                                 // For poller.wait)defer close(w.doneResp)defer close(w.Errors)defer close(w.Events)defer unix.Close(w.fd)defer w.poller.close()for {if w.isClosed() {return}// 监听ok, errno = w.poller.wait()if errno != nil {select {case w.Errors <- errno:case <-w.done:return}continue}if !ok {continue}// 读取就绪的事件 InotifyEvent n, errno = unix.Read(w.fd, buf[:])if errno == unix.EINTR {continue}// 再次判断, 双保险if w.isClosed() {return}// 接下来就是一个事件协议的处理, 中规中矩.// 读取到的内容不足一个事件头if n < unix.SizeofInotifyEvent {var err errorif n == 0 {err = io.EOF // EOF} else if n < 0 {err = errno // reading error} else {err = errors.New("notify: short read in readEvents()") // too short}// Send Errorselect {case w.Errors <- err:case <-w.done:return}continue}// 处理读取的事件, 可能是多个var offset uint32for offset <= uint32(n-unix.SizeofInotifyEvent) {raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) // 直接转换为 InotifyEventmask := uint32(raw.Mask) // 事件nameLen := uint32(raw.Len) // 发生事件的文件的名称(注意是文件, 不是目录)// Send Errorif mask&unix.IN_Q_OVERFLOW != 0 {select {case w.Errors <- ErrEventOverflow:case <-w.done:return}}w.mu.Lock()name, ok := w.paths[int(raw.Wd)]// 监听的文件/目录被删除了, 则移除相应的内容if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {delete(w.paths, int(raw.Wd))delete(w.watches, name)}w.mu.Unlock()// 文件名称(注意: 不是目录)if nameLen > 0 {bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")}event := newEvent(name, mask)// Send Eventif !event.ignoreLinux(mask) {select {case w.Events <- event:case <-w.done:return}}offset += unix.SizeofInotifyEvent + nameLen}}
}
  • 添加监控的目录/文件(相对于监听是异步的)

// 核心在于 InotifyAddWatch 系统调用. 与 epoll 添加关注的 fd 类似, 只是格式不大一样.

func (w *Watcher) Add(name string) error {name = filepath.Clean(name)if w.isClosed() {return errors.New("inotify instance already closed")}// 文件/目录需要通知的事件var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELFw.mu.Lock()defer w.mu.Unlock()watchEntry := w.watches[name]if watchEntry != nil {flags |= watchEntry.flags | unix.IN_MASK_ADD // 对于已存在的条目.}wd, errno := unix.InotifyAddWatch(w.fd, name, flags) // 系统调用if wd == -1 {return errno}if watchEntry == nil {w.watches[name] = &watch{wd: uint32(wd), flags: flags}w.paths[wd] = name // 注册} else {watchEntry.wd = uint32(wd)watchEntry.flags = flags}return nil
}

关于文件变化监听, 你了解多少?相关推荐

  1. spring 文件变化监听_Spring新变化

    spring 文件变化监听 让我们检查一下Spring社区在前几天发布的一些新版本: Spring Boot 1.0.0.RC1 Spring很高兴地宣布Spring Boot v1.0.0的第一个候 ...

  2. vue输入框输入触发事件_.vue文件中监听input输入事件oninput详解

    .vue文件其实是一个组件,关于它的说明我之前也写过一篇文章,地址:.vue文件,今天这篇文章要讲的是.vue文件中监听input的输入值变化事件.需求是这页面中,改变input的值,就调用一个事件, ...

  3. vue输入框输入触发事件_详解.vue文件中监听input输入事件(oninput)

    详解.vue文件中监听input输入事件(oninput) .vue文件其实是一个组件,关于它的说明我之前也写过一篇文章,地址:.vue文件,今天这篇文章要讲的是.vue文件中监听input的输入值变 ...

  4. input框的内容变化监听

    input的两个功能 1.不允许一开始输入空格(即在input框内容为空的时候,输入空格是没有作用的) 2.input框的内容变化监听 这属于比较完美的input的设计了 下面是html代码实现功能1 ...

  5. vue 监听map数组变化_vuex state中的数组变化监听实例

    前言 首先,因为我有一个需求就是vue组件中有一组多选框,选中多选框的内容,要在另一个组件中进行视图更新,这个就设计的兄弟组件之间的通信了,兄弟组件之前通信我首先选用的vuex这个解决办法. 问题 v ...

  6. Vue2源码解析 Object变化监听

    目录 1 什么是变化监听 2 如何跟踪变化 3  何如收集依赖 4  依赖收集在哪 5  依赖是谁 6  什么是Watcher 7  递归侦测所有key 8  关于Object的问题 9  总结 1 ...

  7. hidden隐藏域值变化监听

    hidden隐藏域值变化监听 jQuery.fn.val方法来赋值不会触发change事件,但是可以手动触发. 如:$('#id').val(111).change();

  8. android 实现音量变化监听和静音方法

    android 静音实现方法 类似语音app实现静音与取消静音 1. 模拟按键 模拟静音键 2.调用静音接口 取消静音时,音量条UI显示音量进度与进度值 private AudioManager mA ...

  9. 文件夹监听FileListener

    文件夹监听FileListener 需要实现一个功能,监听某一个文件夹,当该文件夹有任何改动时(新增文件.删除文件),能够实时的获取到这条信息. package com.example.demo.mo ...

  10. 2020-12-04使用retrofit上传下载文件,监听下载进度

    retrofit2上传.下载文件 一.上传文件 1.使用表单上传文件:结合Rxjava 先定义ApiService接口 @Multipart //Multipart表单 @POST("{ur ...

最新文章

  1. 介绍一个.Net资源站点
  2. [翻译]Chameleon介绍(3) : 列表控件
  3. mstar v56几路hdmi_Android TV : Mstar平台 GPIO 调试
  4. 背景-需要-需求规格
  5. PCRE接口pcre_fullinfo混合子模式调用结果
  6. 阻塞非阻塞和同步异步
  7. go语言switch语句用法
  8. 使用WindowsXP中的网桥功能
  9. 一代霸主的没落——诺基亚
  10. 利用xshell通过公钥私钥连接linux服务器
  11. Python中RE模块总结
  12. 分页中PageSize和absolutepage详解
  13. python list[::2]两个分号代表什么意思
  14. Matlab学习手记——输出到MathType公式编辑器
  15. 【微服务】Nacos 注册中心的设计原理
  16. typroa 思维导图_巧用Markdown和百度脑图
  17. linux强制网卡linkup,使用ip link set eth0 up 命令启用网卡后,网络不通的问题的解决...
  18. SteamVR 2.x 手柄拾取3D物体(13)
  19. 天龙八部为什么得到角色信息失败 服务器繁忙《302》,每日最大化获取活跃值的方法分享:卡到499点是关键...
  20. 镗刀的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告

热门文章

  1. getParameterValues
  2. 115怎么利用sha1下载东西_用于批量倾倒和提取的115 sha1工具
  3. 【解决方案】数字孪生智慧光伏电站三维可视化系统
  4. 适用于低配机器,从USB摄像头拉H264流的Qt播放器
  5. 完整的连接器设计手册_工业连接器如何选型
  6. 学术期刊英文标点符号使用规范
  7. 计算机桌面的任务栏,计算机桌面出现两个任务栏怎么办?
  8. 查看App应用签名工具
  9. java swfupload 302_SWFUpload 302
  10. 病毒conime.exe、mmlucj.exe、severe.exe 查杀办法