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