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

共 2502字,需浏览 6分钟

 ·

2021-11-30 23:42

刚开始接触go时,发现go程序和php程序的其中一个不同是php是解释性语言,go是编译型语言,即每次在有程序改动后,需要重新运行 go run或go build进行重新编译,更改才能生效,实则不便。于是乎在网络上搜索发现了gowatch这个包,该包可通过监听当前目录下相关文件的变动,对go文件实时编译,提高研发效率。那gowatch又是如何做到监听文件变化的呢?

通过阅读源码我们发现,在linux内核中,有一种用于通知用户空间程序文件系统变化的机制—Inotify。它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。您还可以跟踪活动的源头和目标等细节。Golang的标准库syscall实现了该机制。为进一步扩展,实现了fsnotify包实现了一个基于通道的、跨平台的实时监听接口。如下图:



根据上图可知,监听文件的变化主要依赖于linux内核的INotify接口机制。Go的标准库中对其做了实现。而fsnotify package的主要作用就是将进一步封装成watcher结构体和事件类型结构体的封装,从而实现事件的判断以及目录的监听。下面看下 fsnotify package中对watcher的封装。

type Watcher struct {
mu sync.Mutex // Map access
fd 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 filter
fsnmut 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 channel
internalEvent chan *FileEvent // Events are queued on this channel
Event chan *FileEvent // Events are returned on this channel
done chan bool // Channel for sending a "quit message" to the reader goroutine
isClosed 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包源代码 。


主要逻辑如下:

  1. 初始化watcher对象

  2. 将文件或目录加入到watcher监控对象的队列

  3. 启动监听协程,实时获取文件对象事件


package main
import (
"fmt"
"github.com/howeyc/fsnotify" "runtime")

var exit chan bool
func main() {    //1、初始化监控对象watcher watcher, 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) } }
<-exit runtime.Goexit()}


推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报