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