​关注微信公众号:“后端开发杂谈”, 领取更多的干货文章.简介: Go 经常会遇到文件变化监控的问题, 这样的问题听起来不太好实现, 那是你没有掌握其中的奥秘, 一旦你理解了其中的原理, 也是能轻松搞定的. 本文主要就是根据 fsnotify 源码, 带你一起认识文件监控的原理.

文件监控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 添加进来.

fsnotify 的工作原理如下:

1. 创建 inotify 文件描述符.(即 SYS_INOTIFY_INIT1 系统调用)

2. 创建 pipe 管道. (非阻塞, 主要的作用是唤醒 goroutine 的作用)

3. 创建 epoll 文件描述符, 通过 epoll 进行事件监听.

4. 将 inotify_fd, pipe_fd 的 EPOLLIN 事件添加到 epoll 当中. (SYS_EPOLL_CTL), 也就说当 inotify_fd 或 pipe_fd

有事件就绪(EPOLLIN, EPOLLHUP, EPOLLERR)之后可以从 epoll 当中获取就绪的事件(SYS_EPOLL_WAIT)

5. 将文件/目录添加到 inotify_fd 监控当中 (SYS_INOTIFY_ADD_WATCH). 主要监控的事件有: 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 是文件权限

6. 开启一个 goroutine, 不断从 epoll 当中读取就绪的事件(SYS_EPOLL_WAIT), 当文件有变化时, 从 inotify_fd 当中读取

事件内容 InotifyEvent, 里面包含了事件, 文件描述符fd, 变更文件的临时名称. 存储格式如下:

InotifyEvent: (头部)

{

Wd int32

Mask uint32

Cookie uint32

Len uint32

}

Data: 长度是 Len

注意: 一次性可能存在多个事件发生.

上面所讲的内容就是文件监控的核心, 利用了 Linux 的 inotify 系统调用来监控通知文件的变更.

接下来的内容主要是从代码的层面来说说文件监控是怎样实现的.

创建 inotify 监听

// 创建一个监听对象

func NewWatcher() (*Watcher, error) {

// 创建 inotify_fd

fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)

if fd == -1 {

return nil, errno

}

// 创建 epoll

poller, err := newFdPoller(fd)

if err != nil {

unix.Close(fd)

return nil, err

}

w := &Watcher{

fd: 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, 并添加监听事件

func newFdPoller(fd int) (*fdPoller, error) {

var errno error

poller := new(fdPoller)

poller.fd = fd

poller.epfd = -1

poller.pipe[0] = -1

poller.pipe[1] = -1

defer func() {

if errno != nil {

poller.close()

}

}()

// 创建 epoll_fd

poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)

if poller.epfd == -1 {

return nil, errno

}

// 创建 pip_fd, 其中 pipe[0] 是读取, pipe[1]是写入. O_NONBLOCK 表示非阻塞

errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)

if errno != nil {

return nil, errno

}

// 注册 inotify_fd "读" 的事件到 epoll_fd

event := unix.EpollEvent{

Fd: int32(poller.fd),

Events: unix.EPOLLIN,

}

errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)

// 注册 pip_fd "读" 的事件到 epoll_fd

event = 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 的监控事件

// 读取 inotify 就绪的事件, 并且通知客户端

func (w *Watcher) readEvents() {

var (

buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events

n int // Number of bytes read with read()

errno error // Syscall errno

ok 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

}

// epoll 的 SYS_EPOLL_WAIT 系统调用, 判断是否有读取事件就绪

ok, errno = w.poller.wait()

if errno != nil {

// Send Error

select {

case w.Errors

case

return

}

continue

}

if !ok {

continue

}

// inotify_fd 就绪, 读取就绪的事件 InotifyEvent

n, errno = unix.Read(w.fd, buf[:])

if errno == unix.EINTR {

continue

}

// 再次判断, 双保险

if w.isClosed() {

return

}

// 读取到的内容不足一个事件头

if n < unix.SizeofInotifyEvent {

var err error

if 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 Error

select {

case w.Errors

case

return

}

continue

}

// 处理读取的事件, 可能是多个

var offset uint32

for offset <= uint32(n-unix.SizeofInotifyEvent) {

raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) // 直接转换为 InotifyEvent

mask := uint32(raw.Mask) // 事件

nameLen := uint32(raw.Len) // 发生事件的文件的名称(注意是文件, 不是目录)

// Send Error

if mask&unix.IN_Q_OVERFLOW != 0 {

select {

case w.Errors

case

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 Event

if !event.ignoreLinux(mask) {

select {

case w.Events

case

return

}

}

offset += unix.SizeofInotifyEvent + nameLen

}

}

}

epoll wait, 监听就绪事件

func (poller *fdPoller) wait() (bool, error) {

// 2个fd, fd监听 EPOLLIN, 同时 EPOLLHUP, EPOLLERR 是无须监听, 任何时候都会发生

// 取最大值 2*3+1

events := make([]unix.EpollEvent, 7)

for {

n, errno := unix.EpollWait(poller.epfd, events, -1) // n是就绪的事件个数

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")

}

// 就绪的事件的判断

ready := events[:n]

epollhup := false

epollerr := false

epollin := false

for _, event := range ready {

if event.Fd == int32(poller.fd) {

if event.Events&unix.EPOLLHUP != 0 {

// This should not happen, but if it does, treat it as a wakeup.

epollhup = true

}

if event.Events&unix.EPOLLERR != 0 {

// If an error is waiting on the file descriptor, we should pretend

// something is ready to read, and let unix.Read pick up the error.

epollerr = true

}

if event.Events&unix.EPOLLIN != 0 {

// There is data to read.

epollin = true

}

}

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 {

// This is a regular wakeup, so we have to clear the buffer.

err := poller.clearWake()

if err != nil {

return false, err

}

}

}

}

if epollhup || epollerr || epollin {

return true, nil

}

return false, nil

}

}

添加监控的目录/文件

func (w *Watcher) Add(name string) error {

name = filepath.Clean(name)

if w.isClosed() {

return errors.New("inotify instance already closed")

}

// 文件/目录需要通知的事件

const agnosticEvents = 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_SELF

var flags uint32 = agnosticEvents

w.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

}

总结:文件监控的核心是利用了 inotify 系统调用, 监听文件的变化, 然后通知到应用层面.

为了避免每次通知之后又需要再次进行系统调用所带来的内存拷贝问题, 又使用了epoll 来监听 inotify 是事件, 在事件可读的时候, 再次通知用户. 从而提高效率.

epoll监听文件_Go 文件监控怎么实现?相关推荐

  1. epoll监听文件_【原创】万字长文浅析:Epoll与Java Nio的那些事儿

    " Epoll 是Linux内核的高性能.可扩展的I/O事件通知机制. 在linux2.5.44首次引入epoll,它设计的目的旨在取代既有的select.poll系统函数,让需要大量操作文 ...

  2. epoll监听文件_介绍一下 Android Handler 中的 epoll 机制?

    介绍一下 Android Handler 中的 epoll 机制? 目录: IO 多路复用 select.poll.epoll 对比 epoll API epoll 使用示例 Handler 中的 e ...

  3. 【Python|第39期】监听目录取消文件只读属性

    日期:2023年7月17日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海-- 注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长:如果有不对的地方,还望各位 ...

  4. Handler ,MessageQueue 的Looper中epoll监听的fd

    hi,同学们大家好! 这些天有学员再群里问起了Handler中有个数据监听相关问题,学员有的认为Handler数据传递是靠流传递,误认为是epoll中监听的fd进行传递的,这个其实有必要更正这个学员的 ...

  5. linux epoll监听套接字实例

    linux epoll机制用于IO多路复用,能够同时监听多个接字,使用起来比较简单. 相关接口: #include <sys/epoll.h>int epoll_create(int si ...

  6. 监听列表事件的监控核心技术(编写代码)

    这一段代码是根据上一篇"监听列表事件的监控"所编写的,在onmousemove事件中,可以通过事件对象获取到鼠标当前的坐标点,我们该如何将坐标点转化成为元素的left和top属性值 ...

  7. epoll监听文件_epoll使用详解

    epoll介绍 epoll的行为与poll(2)相似,监视多个有IO事件的文件描述符.epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触 ...

  8. epoll监听文件_epoll详解——从功能到内核

    首先我们了解一下什么是I/O复用.I/O就是指网络中的I/O(即输入输出),多路是指多个TCP连接,复用是指一个或少量线程被重复使用.连起来理解就是,用少量的线程来处理网络上大量的TCP连接中的I/O ...

  9. FileAlterationMonitor监听目录下文件变化

    工具类 FileMonitor package com.ncs.wavtrans.utils;import org.apache.commons.io.monitor.FileAlterationLi ...

  10. oracle启动监听读取哪个文件,监听服务启动及数据文件恢复oracle数据库

    最近遭遇了 oralce 监听服务启动了 又自行关闭的 悲惨经历 我把我的过程和大家分享一下! 1)排查原因 程序员是懒惰的,我始终都希望能够成功启动监听服务,但是就是事与愿违 有一下方式可能不能成功 ...

最新文章

  1. 1012: [JSOI2008]最大数maxnumber
  2. Python基础教程:迭代和解析
  3. thymeleaf 异常:SpelEvaluationException: EL1008E: Property or field ‘url‘ cannot be found
  4. 机器学习实战(用Scikit-learn和TensorFlow进行机器学习)(五)
  5. php仿微信上传图片压缩,PHP仿微信多图片预览上传实例代码
  6. 遗传算法学习笔记(一):常用的选择策略
  7. 超级计算机子系统,大规模并行巨型机的并行I/O子系统
  8. 使用信号实现异步通知机制的例子
  9. 使用PL/SQL删除百万条记录的大表
  10. Windows下保存git账号密码实现免输入
  11. el-table对于超出长度限制的文本的处理(vue-cli)
  12. 高性能mysql之慎用BLOB与TEXT
  13. c语言编程跑马灯,走楼灯设计(C语言设计跑马灯程序)
  14. 神经网络模型的工作原理,神经网络模型数据处理
  15. 禁止AutoCAD联网
  16. iOS 15:Spotlight 搜索中的所有新功能
  17. 金蝶软件认证显示服务器异常,金蝶提示云服务器异常
  18. 小米路由器AX9000刷写OpenWrt官网发布的固件
  19. 2021-2027全球与中国可待因药品市场现状及未来发展趋势
  20. npm下载swiper包报错

热门文章

  1. 软件项目管理:使用PERT评价不确定性的方法
  2. ITF跆拳道的24个特尔
  3. 二级计算机题世界动物日,计算机二级考试真题-PPT-张宇-世界动物日介绍
  4. 寻找“最好”(2)——欧拉-拉格朗日方程
  5. ASP.NET Core MVC 入门到精通 - 1. 开发必备工具 (2021)
  6. 手机测试光纤网速的软件,如何测试光纤网速?如何测试网速多少兆
  7. python代码示例-Python代码样例列表
  8. linux卡片电脑设计,ThinkPad重大更新!5款创意设计PC齐发2日
  9. Word模板引擎使用指南
  10. 接上文安装完opencv后安装viz模块