浅析gowatch监听文件变动实现原理

根据上图可知,监听文件的变化主要依赖于linux内核的INotify接口机制。Go的标准库中对其做了实现。而fsnotify package的主要作用就是将进一步封装成watcher结构体和事件类型结构体的封装,从而实现事件的判断以及目录的监听。下面看下 fsnotify package中对watcher的封装。
type Watcher struct {mu sync.Mutex // Map accessfd int // File descriptor (as returned by the inotify_init() syscall)watches map[string]*watch // Map of inotify watches (key: path)fsnFlags map[string]uint32 // Map of watched files to flags used for filterfsnmut sync.Mutex // Protects access to fsnFlags.paths map[int]string // Map of watched paths (key: watch descriptor)Error chan error // Errors are sent on this channelinternalEvent chan *FileEvent // Events are queued on this channelEvent chan *FileEvent // Events are returned on this channeldone chan bool // Channel for sending a "quit message" to the reader goroutineisClosed bool // Set to true when Close() is first called}
linux内核Inotify接口简介
inotify中主要涉及3个接口。分别是inotify_init, inotify_add_watch,read。具体如下:
| 接口名 | 作用 |
| int fd = inotify_init() | 创建inotify实例,返回对应的文件描述符 |
| inotify_add_watch (fd, path, mask) | 注册被监视目录或文件的事件 |
| read (fd, buf, BUF_LEN) | 读取监听到的文件事件 |
Inotify可以监听的文件系统事件列表:
| 事件名称 | 事件说明 |
IN_ACCESS | 文件被访问 |
IN_MODIFY | 文件被 write |
IN_CLOSE_WRITE | 可写文件被 close |
IN_OPEN | 文件被 open |
IN_MOVED_TO | 文件被移来,如 mv、cp |
IN_CREATE | 创建新文件 |
IN_DELETE | 文件被删除,如 rm |
IN_DELETE_SELF | 自删除,即一个可执行文件在执行时删除自己 |
| IN_MOVE_SELF | 自移动,即一个可执行文件在执行时移动自己 |
| IN_ATTRIB | 文件属性被修改,如 chmod、chown、touch 等 |
| IN_CLOSE_NOWRITE | 不可写文件被 close |
| IN_MOVED_FROM | 文件被移走,如 mv |
| IN_UNMOUNT | 宿主文件系统被 umount |
| IN_CLOSE | 文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) |
| IN_MOVE | 文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO) |
示例应用
接下来是一个简易的示例应用,具体的应用实例可参考github.com/silenceper/gowatch包源代码 。
主要逻辑如下:
初始化watcher对象
将文件或目录加入到watcher监控对象的队列
启动监听协程,实时获取文件对象事件
package mainimport ("fmt""github.com/howeyc/fsnotify""runtime")var exit chan boolfunc main() {//1、初始化监控对象watcherwatcher, err := fsnotify.NewWatcher()if err != nil {fmt.Printf("Fail to create new Watcher[ %s ]\n", err)}//3、启动监听文件对象事件协程go func() {fmt.Println("开始监听文件变化")for {select {case e := <-watcher.Event:// 这里添加根据文件变化的业务逻辑fmt.Printf("监听到文件 - %s变化\n", e.Name)if e.IsCreate() {fmt.Println("监听到文件创建事件")}if e.IsDelete() {fmt.Println("监听到文件删除事件")}if e.IsModify() {fmt.Println("监听到文件修改事件")}if e.IsRename() {fmt.Println("监听到文件重命名事件")}if e.IsAttrib() {fmt.Println("监听到文件属性修改事件")}fmt.Println("根据文件变化开始执行业务逻辑")case err := <-watcher.Error:fmt.Printf(" %s\n", err.Error())}}}()// 2、将需要监听的文件加入到watcher的监听队列中paths := []string{"config.yml"}for _, path := range paths {err = watcher.Watch(path) //将文件加入监听if err != nil {fmt.Sprintf("Fail to watch directory[ %s ]\n", err)}}<-exitruntime.Goexit()}
推荐阅读
评论
