工作中我們可能會遇到一些問題,比如系統部署過程中配置文件在多個主機之間的同步問題,或是和其他系統對接的時候,需要以其他系統輸出的文件作為輸入的時候,這時需要我們實時的監控文件目錄的變化,用以做出響應。通常我們可能的選擇是實時的監測目錄信息,不斷去獲取目錄信息來判斷文件目錄是否有變化。但在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
這樣就配置完成了。