文件监控直接通过了linux的inotify接口实现。这里没有考虑移植性,也就没像tailf那样,通过宏来判断是否支持inotify,如果不支持,降级使用循环轮寻的方式读取。
inotify的使用还是比较方便的基本上就是:inotify_init,inotify_add_watch,然后配合read系统调用,获取文件修改信息。因此实现也非常方便。
首先是在构造函数里面初始化inotify:
[cce lang="cpp"]
inotifyFd = inotify_init();
[/cce]
然后提供一个watch接口,通过传入前文描述的TFile对象和内容读取的回调函数,添加对应文件的监控和回调。
[cce lang="cpp"]
void FileWatcher::watch ( boost::shared_ptr< TFile > tFile, std::list< FileWatcher::ReadCallBack > callBackList )
{
if(!tFile->hasError() && !callBackList.empty()) {
int wd = inotify_add_watch(inotifyFd, tFile->name().c_str(), IN_MODIFY);
if(wd > 0) {
tFileMap.insert(std::make_pair<int, boost::shared_ptr<TFile> >(wd, tFile));
callBackMap.insert(std::make_pair<int, std::list<ReadCallBack> >(wd, callBackList));
//init read
std::string initContent = tFile->read();
BOOST_FOREACH(ReadCallBack &callback, callBackList) {
callback(initContent);
}
}
}
}
[/cce]
这里通过TFile的文件名,向内核注册添加该文件的modify事件,并且在注册成功之后,进行初始读取(这里有个小问题,由于后面websocket端没有做缓存,所以由于初始读取的时候还没有任何websocket客户端连接,所以通过web无法读取初始内容,也就是文件最后10行)。同时,这个类维护两个hashmap,分别是监听描述符wd->tFile和wd->callbacklist。
监听完成后,就是启动监听,也就是通过读取fd,感知被监听文件的变更,由于这里只监听了文件修改,那么读取到这个事件之后,就可以对该文件进行增量读取(前文已经描述了读取方法)。
[cce lang="cpp"]
char * buffer = new char[inotifyBufferSize];
while(!_interrupted) {
if(read(inotifyFd, buffer, inotifyBufferSize) < 0) {
if(errno == EINTR) {
// interrupt
delete buffer;
return;
}
}
struct inotify_event *event = ( struct inotify_event * ) buffer;
int wd = event->wd;
BOOST_AUTO(tFileIter, tFileMap.find(wd));
if(tFileIter != tFileMap.end()) {
boost::shared_ptr<TFile> tFile = tFileIter->second;
std::string content = tFile->read();
BOOST_AUTO(iter, callBackMap.find(wd));
if(iter != callBackMap.end()) {
std::list<ReadCallBack> callbacks = iter->second;
BOOST_FOREACH(ReadCallBack &callback, callbacks) {
callback(content);
}
}
}
}
delete buffer;
[/cce]
这里参照inotify的文档,首先读取缓冲区大小设置为
[cce lang="cpp"]
static const int inotifyBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1;
[/cce]
也就是inotify_event结构的长度,和名字最大值。由于inotify_event是变长字段(包含一个可选的文件名字段),所以这里采用了系统限制的文件名最大值NAME_MAX,这个宏在climits中定义,在linux中大小为255字节。
然后通过系统调用read,读取文件描述符inotifyFd,这里如果没有新的事件产生,read会进入阻塞状态,节省系统资源。如果有返回,则处理返回的inotify_event对象(注意在监听modify事件的时候,是没有文件名的)。通过结构中的wd,从之前保存的hashmap中获取对应的tFile对象进行增量读取,然后再读取wd对应的回调函数,将读取内容返回。
这里有个小问题需要处理,就是如何中断读取。之前为了在gtest中能够通过单元测试的方式进行测试,通过查看手册可以知道,如果read调用被系统信号中断,会标记错误码为EINTR。所以,当读取失败的时候,可以通过对ERRNO检查,判断是否是信号中断。
由于程序会一直运行,知道通过信号终止,所以析构变的不是很重要了。这里析构函数里面通过调用inotify_rm_watch将之前保存的wd全部去掉,然后通过close调用交inotify的文件描述符关闭即可:
[cce lang="cpp"]
FileWatcher::~FileWatcher()
{
if(inotifyFd > 0) {
boost::unordered::unordered_map<int, boost::shared_ptr<TFile> >::iterator iter;
for(iter = tFileMap.begin(); iter != tFileMap.end(); ++iter) {
inotify_rm_watch(inotifyFd, iter->first);
}
close(inotifyFd);
}
}
[/cce]