工作中我们可能会遇到一些问题,比如系统部署过程中配置文件在多个主机之间的同步问题,或是和其他系统对接的时候,需要以其他系统输出的文件作为输入的时候,这时需要我们实时的监控文件目录的变化,用以做出响应。通常我们可能的选择是实时的监测目录信息,不断去获取目录信息来判断文件目录是否有变化。但在linux系统下,系统内核提供了一个机制Inotify,用以通知文件目录的变化。

Inotify 是一个 Linux特性,它监控文件系统操作,比如读取、写入和创建。Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多。详细说明大家可以查询相关资料。

使用Inotify的基本步骤

一、初始化,并指定监控目录



int fd=inotify_init();//初始化
int wd=inotify_add_watch(fd,path.c_str(),IN_MODIFY|IN_CREATE|IN_DELETE);//添加监视



 

下面分享我在工作实现的一个简单监控目录小程序:

程序主要包括三个文件:filemonitor.h,filemonitor.cpp和main.cpp



1 #ifndef FILEMONITOR_H
 2 #define FILEMONITOR_H
 3 #include<iostream>
 4 #include<map>
 5 #include<vector>
 6 using namespace std;
 7 enum EventType{
 8     CREATE,
 9     DELETE,
10     MODIFY
11 };
12 
13 typedef struct{
14     string path;//路径
15     EventType type;//事件类型
16     int fileType;//0表示文件夹,1表示文件
17 } Event;
18 typedef struct{
19     string path;//文件路径
20     int expire;//过期时间
21 } FileInfo;
22 class FileMonitor
23 {
24 private:
25     string path;//监控根目录
26     int fd;//句柄
27     map<int,string> monitorMap;//监控列表
28     long expire;//文件过期时间
29     vector<FileInfo *> fileList;
30     void monitorSubFolder(string path);
31     void (*listener)(const Event  *);
32     void deleteFile();
33 public:
34     FileMonitor(string path,void (*listener)(const Event  *),int expire=180);
35     ~FileMonitor();
36     int start();
37 };
38 
39 #endif // FILEMONITOR_H



1 #include "filemonitor.h"
  2 #include<string.h>
  3 #include<iostream>
  4 #include <stdio.h>
  5 #include <dirent.h>
  6 #include<stdlib.h>
  7 #include<sys/types.h>
  8 #include<sys/inotify.h>
  9 #include<time.h>
 10 using namespace std;
 11 #define EVENT_SIZE  ( sizeof (struct inotify_event) )
 12 #define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )
 13 FileMonitor::FileMonitor(string path,void (*listener)(const Event  *),int expire)
 14 {
 15     this->listener=listener;
 16     this->expire=60;//expire*24*60*60;
 17     fd=inotify_init();
 18     if(fd<0)
 19     {
 20         cout<<"innotify_init failed"<<endl;
 21     }
 22     monitorSubFolder(path);
 23 }
 24 int FileMonitor::start()
 25 {
 26 
 27     char buffer[BUF_LEN];
 28     bool start=true;
 29     while(start)
 30     {
 31         int length=0,i=0;
 32         length=read(fd,buffer,BUF_LEN);
 33         if(length<0)
 34         {
 35             cout<<"read none"<<endl;
 36         }
 37         while ( i < length )
 38         {
 39             struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
 40             if ( event->len &&event->name[0]!='.'&&event->name[strlen(event->name)-1]!='~') //同时过滤隐藏文件和临时文件
 41             {
 42                 if ( event->mask & IN_CREATE )
 43                 {
 44                     Event *args=new Event;
 45                     if ( event->mask & IN_ISDIR )
 46                     {
 47                         string path=monitorMap[event->wd]+"/"+event->name;
 48                         args->path=path;
 49                         args->fileType=0;
 50                         args->type=CREATE;
 51                         monitorSubFolder(path);
 52                     }
 53                     else
 54                     {
 55                         string path=monitorMap[event->wd]+"/"+event->name;
 56                         args->path=path;
 57                         args->fileType=1;
 58                         args->type=CREATE;
 59                         time_t now;
 60                         now=time(NULL);
 61                         FileInfo * fileInfo=new FileInfo();//将文件信息添加到监控列表中
 62                         fileInfo->path=path;
 63                         fileInfo->expire=now+expire;//设置过期时间
 64                         if(strcmp(event->name,"stop")==0)//如果新建文件名为stop则退出程序
 65                         {
 66                             fileInfo->expire=now;//设置过期时间
 67                             start=false;
 68                         }
 69                         fileList.push_back(fileInfo);
 70                     }
 71                     if(listener!=NULL)
 72                     {
 73                         listener(args);
 74                     }
 75                 }
 76                 else if ( event->mask & IN_DELETE )
 77                 {
 78                     Event *args=new Event;
 79                     if ( event->mask & IN_ISDIR )
 80                     {
 81                         string path=monitorMap[event->wd]+"/"+event->name;
 82                         args->path=path;
 83                         args->fileType=0;
 84                         args->type=DELETE;
 85                         inotify_rm_watch(fd,event->wd);//移除监视
 86                         monitorMap.erase(event->wd);//
 87                     }
 88                     else {
 89                         string path=monitorMap[event->wd]+"/"+event->name;
 90                         args->path=path;
 91                         args->fileType=1;
 92                         args->type=DELETE;
 93                     }
 94                     if(listener!=NULL)
 95                     {
 96                         listener(args);
 97                     }
 98                 }
 99                 else if ( event->mask & IN_MODIFY )
100                 {
101                     Event *args=new Event;
102                     if ( event->mask & IN_ISDIR )
103                     {
104                         string path=monitorMap[event->wd]+"/"+event->name;
105                         args->path=path;
106                         args->fileType=0;
107                         args->type=MODIFY;
108                     }
109                     else {
110                         string path=monitorMap[event->wd]+"/"+event->name;
111                         args->path=path;
112                         args->fileType=1;
113                         args->type=MODIFY;
114                     }
115                     if(listener!=NULL)
116                     {
117                         listener(args);
118                     }
119                 }
120 
121             }
122             i += EVENT_SIZE + event->len;
123         }
124         deleteFile();
125     }
126     return 0;
127 }
128 //监控子目录
129 void FileMonitor::monitorSubFolder(string path)
130 {
131     int wd=inotify_add_watch(fd,path.c_str(),IN_MODIFY|IN_CREATE|IN_DELETE);
132     monitorMap[wd]=path;//添加到监视列表
133     struct dirent* ent = NULL;
134     DIR *pDir;
135     pDir=opendir(path.c_str());
136     if(pDir==NULL)
137     {
138         cout<<"invalid path"<<endl;
139         return;
140     }
141     while (NULL != (ent=readdir(pDir)))
142     {
143         if(ent->d_type==4){
144             if(ent->d_name[0]!='.')
145             {
146                 string subPath=path+"/"+ent->d_name;
147                 monitorSubFolder(subPath);
148             }
149         }else{
150             if(listener!=NULL)
151             {
152                 int len=strlen(ent->d_name);
153                 if(ent->d_name[0]!='.'&&ent->d_name[len-1]!='~')
154                 {
155                     string filename=path+"/"+ent->d_name;
156                     Event *args=new Event;
157                     args->path=filename;
158                     args->fileType=1;
159                     args->type=CREATE;
160                     time_t now;
161                     now=time(NULL);
162                     FileInfo * fileInfo=new FileInfo();//将文件信息添加到监控列表中
163                     fileInfo->path=filename;
164                     fileInfo->expire=now+expire;//设置过期时间
165                     fileList.push_back(fileInfo);
166                     listener(args);
167                 }
168 
169             }
170 
171         }
172     }
173 }
174 void FileMonitor::deleteFile()
175 {
176     time_t now;
177     now=time(NULL);
178     for(vector<FileInfo *>::iterator it=fileList.begin();it!=fileList.end();)
179     {
180         if((*it)->expire<=now)
181         {
182             if(!remove((*it)->path.c_str()))
183             {
184                 cout<<(*it)->path<<" expired has been deleted"<<endl;
185                 it=fileList.erase(it);
186             }else{
187                 cout<<"delete "<<(*it)->path<<" failure"<<endl;
188                 it++;
189             }
190         }else
191         {
192             it++;
193         }
194     }
195 }
196 FileMonitor::~FileMonitor()
197 {
198     for (map<int, string>::iterator i=monitorMap.begin(); i!=monitorMap.end(); i++)
199     {
200         inotify_rm_watch(fd,i->first);//移除监视
201     }
202     monitorMap.clear();
203     close(fd);
204 }



 

如果大家不想自己写代码实现文件监控,现在也有一个工具能够帮助我们监控文件目录变化,这就是inotify-tools,这个工具配合rsync可以实现文件实时同步的功能。

下面给大家介绍一下这个工具的使用方法

测试主机地址:A、192.168.20.9
B、192.168.20.20
测试目的将A主机目录/home/dmx/sync/test/的内容同步到B主机 /home/d5000/dmx/test目录下
任意修改A主机同步目录下的内容,对应B主机的目录也会自动修改。

需要安装的软件有
inotify-tools-3.14.tar.gz
rsync-3.0.9.tar.gz
软件装的基本步骤
1、解压 进到解压后的目录
2、执行./configure
3、执行make
4、执行make install
服务端配置
服务端需要安装rsync和inotify
1、创建密码文件
echo "d5000">rsync.passwd
其中"d5000"表示登录B主机的密码,这里用户名不需要指定,稍后将会在脚本中设置
2、创建执行脚本
测试脚本位于startSync.sh
#!/bin/bash
SRC='/home/dmx/sync/test/'
DES='d5000@192.168.20.20::web'
/usr/local/bin/inotifywait -mr --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' -e modify,delete,create,attrib ${SRC} |while read DATE TIME DIR FILE
do
FILECHAGE=${DIR}${FILE}
/home/dmx/rsync-3.0.9/rsync -av --progress --delete --password-file=/home/dmx/rsync-3.0.9/rsync.passwd $SRC $DES #执行同步命令
echo "At ${TIME} on ${DATE}, file $FILECHAGE was backed up via rsync" >> /home/dmx/sync/rsyncd.log
done
脚本说明:
SRC表示A主机同步的目录,DES表示B主机的地址,其中web表示一个模块,名称可以自定义
--password-file=/home/dmx/rsync-3.0.9/rsync.passwd指定密码文件的位置。其中,密码文件只能被当前运行脚本的用户访问,对应的命令是:chmod 600 rsync.passwd
/home/dmx/sync/rsyncd.log指定日志文件的存放位置
如果需要同步到多台主机,需要指定不同的DES,同时执行多条同步命令
脚本示例:
#!/bin/bash
SRC='/home/dmx/sync/test/'
DES1='d5000@192.168.20.20::web1'
DES2='d5000@192.168.20.21::web2'
/usr/local/bin/inotifywait -mr --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' -e modify,delete,create,attrib ${SRC} |while read DATE TIME DIR FILE
do
FILECHAGE=${DIR}${FILE}
/home/dmx/rsync-3.0.9/rsync -av --progress --delete --password-file=/home/dmx/rsync-3.0.9/rsync.passwd $SRC $DES1 #执行同步命令
/home/dmx/rsync-3.0.9/rsync -av --progress --delete --password-file=/home/dmx/rsync-3.0.9/rsync.passwd $SRC $DES2 #执行同步命令
echo "At ${TIME} on ${DATE}, file $FILECHAGE was backed up via rsync" >> /home/dmx/sync/rsyncd.log
done
客户端配置
客户端只需要安装rsync
1、创建密码文件
echo "d5000:d5000">rsync.passwd
格式为"用户名:密码"
2、创建rsync.conf配置文件
客户端配置文件内容
uid = nobody
gid = nobody
use chroot = no
max connections = 10
strict modes = yes
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
[web]#web表示模块,这里和A主机指定的模块相同
path = /home/d5000/dmx/test #表示本地同步的目录
comment = web file
ignore errors
read only = no
write only = no
hosts allow =192.168.20.9 #允许连接的主机地址
hosts deny = *
list = false
uid = root
gid = root
auth users = d5000 #登录用户
secrets file = /home/d5000/dmx/rsync-3.0.9/rsync.passwd #密码文件位置
密码文件必须只能被运行rsync的用户访问,创建时以运行用户创建,然后执行chmod 600 rsync.passwd
运行./rsync --daemon --config=/home/d5000/dmx/rsync-3.0.9/rsync.conf

这样就配置完成了。