浅析gowatch监听文件变动实现原理
根据上图可知,监听文件的变化主要依赖于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包源代码 。
主要逻辑如下:
初始化watcher对象
将文件或目录加入到watcher监控对象的队列
启动监听协程,实时获取文件对象事件
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()
}
推荐阅读
评论