使用libev监视文件夹下文件(夹)属性变动的方案和实现
Posted breaksoftware
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用libev监视文件夹下文件(夹)属性变动的方案和实现相关的知识,希望对你有一定的参考价值。
在《libev源码解析》系列中,我们分析了libev的基本原理。本文我们介绍一套使用libev封装的文件(夹)变动监视方案和实现。(转载请指明出于breaksoftware的csdn博客)
我们先看个最简单方案,下面的代码会监视/home/work下文件(夹)的新增、删除等操作。
void call_back(ev::stat &w, int revents)
std::cout << "watch " << w.path << std::endl;
int main()
ev::default_loop loop;
ev::stat state;
state.set(call_back);
state.set(loop);
state.start("/home/work/");
loop.run();
return 0;
第6行,我们使用了默认的loop。除了default_loop,libev还提供了dynamic_loop。如果我们没有指定loop,则libev会使用默认的。
第7行,我们声明了文件(夹)监视器state。
第8行,将回调函数call_back和监视器关联。
第9行,将loop和监视器关联。
第10行,监视器开始监视目录/home/work。
第11行,让loop运行起来以阻塞住进程。
这样一旦目录下有文件(夹)变动,则会调用回调函数call_back。
假如这种方式可以涵盖所有情况,那么也不会存在这篇博文了。因为上述方案存在如下缺陷:
- 堵塞主线程
- call_back的stat::path一直指向被监视的文件(夹)路径。这样在监控一个文件夹时,如果有子文件(夹)新增或者删除,我们都将无法从回调函数中得知变动的是谁。
- 子文件夹下有文件新增监控不到。
- 如果监视一个文件夹时发生子文件的复制覆盖行为,将监视不到。
第1个问题并不严重,我们只要启动一个线程便可解决。第2个问题,我们可以通过对比变动前后的目录结构去解决,也不算太复杂。第3个问题,我们需要对每个子目录进行监控,并且在有新文件夹被创建时新增监控,在有文件夹被删除时删除监控。第4个问题则比较严重了。要解决第4个问题,我们需要对文件夹的监视精细到具体的文件级别,也就是说不是笼统的对某个目录进行监视,而是还要对目录下每个文件进行监视。
于是对一个文件夹的监视,需要做到:
- 监视该文件夹,以获取新增文件(夹)信息。
- 监视该文件夹下所有子文件,以获取复制覆盖信息。
- 监视该文件夹下所有子文件夹,以监视子文件夹下文件的新增及其后续操作。
- 对于新增的文件(夹),需要新增监视。
- 对于删除的文件(夹),需要删除监视。
- 对于文件夹监视器和文件监视器重复上报的行为(删除文件)需要去重处理。
由于loop会堵塞住线程,所以我们让一个loop占用一个线程。多个监视器可关联到一个loop。但是监视器和loop的关系存在如下情况:
- 如果有多个监视器关联到一个loop,则一个监视器停止后,loop仍会堵塞住线程。
- 如果只有一个监视器关联到loop,那这个监视器停止后,loop会从堵塞状态中跳出。
我希望监视器可以关联到同一个loop,于是对loop做了如下封装
class LibevLoop
public:
LibevLoop();
~LibevLoop();
template<class T>
friend void bind(T& a, LibevLoop* b);
public:
void run_loop();
private:
ev::dynamic_loop loop_;
std::timed_mutex timed_mutex_;
;
template<class T>
void bind(T& a, LibevLoop* b)
a.set(b->loop_);
LibevLoop::~LibevLoop()
loop_.break_loop(ev::ALL);
void LibevLoop::run_loop()
if (timed_mutex_.try_lock_for(std::chrono::milliseconds(1)))
std::thread t([this]
timed_mutex_.lock();
loop_.run();
timed_mutex_.unlock();
);
t.detach();
timed_mutex_.unlock();
由于ev::dynamic_loop是内部管理对象,我并不希望暴露出它,于是提供了一个友元函数bind供外部使用。其实这个地方使用模板函数并不是很合适,最好是针对具体类的方法。
run_loop函数内部使用超时锁检测loop是否在运行,从而可以保证各个线程调用该函数时只有一个线程被运行。
我们再封装了一个监视器类
class Watcher
public:
using callback = std::function<void(ev::stat&, int)>;
Watcher() = delete;
explicit Watcher(const std::string& path, callback c, LibevLoop* loop = nullptr);
~Watcher();
private:
void callback_(ev::stat &w, int revents);
private:
callback cb_;
ev::stat state_;
;
Watcher::Watcher(const std::string& path, callback c, LibevLoop* loop)
if (!loop)
static LibevLoop loop_;
loop = &loop_;
cb_.swap(c);
state_.set<Watcher, &Watcher::callback_>(this);
bind(state_, loop);
state_.start(path.c_str());
loop->run_loop();
Watcher::~Watcher()
state_.stop();
void Watcher::callback_(ev::stat &w, int revents)
cb_(w, revents);
Watcher的构造函数执行的是文中最开始给出的libev的调用过程。区别是loop被替换为之前定义的LibevLoop,从而不会在该步堵塞线程。
现在我们可以实现监视器中最基础的文件监视器。
class FileWatcher
public:
using callback = std::function<void(const std::string& path, FileWatcherAction action)>;
FileWatcher() = delete;
~FileWatcher();
explicit FileWatcher(const std::string& path, callback cb, LibevLoop* loop = nullptr);
private:
void watch_(ev::stat&, int);
private:
callback cb_;
std::string file_path_;
std::time_t last_write_time_ = 0;
std::shared_ptr<Watcher> watcher_;
;
FileWatcher::~FileWatcher()
FileWatcher::FileWatcher(const std::string& path, callback cb, LibevLoop* loop)
file_path_ = absolute(path);
cb_ = std::move(cb);
if (boost::filesystem::is_directory(file_path_))
return;
if (boost::filesystem::is_regular_file(file_path_))
last_write_time_ = boost::filesystem::last_write_time(file_path_);
watcher_ = std::make_shared<Watcher>(file_path_,
std::bind(&FileWatcher::watch_, this, std::placeholders::_1, std::placeholders::_2), loop);
void FileWatcher::watch_(ev::stat &w, int revents)
if (!boost::filesystem::is_regular_file(file_path_))
if (last_write_time_ != 0)
cb_(file_path_, FILE_DEL);
return;
std::time_t t = boost::filesystem::last_write_time(file_path_);
if (last_write_time_ != t)
FileWatcherAction ac = (last_write_time_ == 0) ? FILE_NEW : FILE_MODIFY;
cb_(file_path_, ac);
由于libev需要监视的路径是绝对路径,所以FileWatcher函数会先通过absolute函数修正路径。
std::string absolute(const std::string& path)
if (boost::filesystem::path(path).is_absolute())
return path;
std::string absolute_path = boost::filesystem::system_complete(path).string();
return absolute_path;
然后获取该文件的最后修改时间。
FileWatcher::watch_函数是回调函数,它一开始检测文件是否存在,如果不存在且之前存在(最后修改时间不为0),则发起通知。如果文件存在,则通过通过对比最后修改时间来确定发生的行为是“新增”还是“修改”。
接下来就要接触到比较复杂的文件夹监视。之前我们提到过,需要对目录下所有文件进行监视,并且需要遍历整个目录以确定新增的是哪个文件。于是就设计了一个遍历目录的方法
using callback = std::function<void(const std::string&)>;
void folder_scan(const std::string& path, callback file_cb, callback folder_cb)
if (!boost::filesystem::is_directory(path))
return;
if (!boost::filesystem::exists(path))
return;
boost::filesystem::directory_iterator it(path);
boost::filesystem::directory_iterator end;
for (; it != end; it++)
if (boost::filesystem::is_directory(*it))
folder_cb(it->path().string());
folder_scan(it->path().string(), file_cb, folder_cb);
else
file_cb(it->path().string());
folder_scan方法提供了两个回调,一个是在扫描到文件时调用,一个是扫描到文件夹时调用。
对比文件夹下文件(夹)新增的类将使用上述方法实现对比操作。
enum PathType
E_FILE = 0,
E_FOLDER,
;
struct PathInfo
std::string path;
PathType type;
bool operator < (const PathInfo & right) const
return path.compare(right.path) < 0;
;
using PathInfoSet = std::set<PathInfo>;
class FolderDiff
public:
explicit FolderDiff(const std::string& path);
void diff(PathInfoSet & add, PathInfoSet & remove);
private:
void scan_(const std::string& path, PathType type, PathInfoSet& path_infos);
private:
std::string folder_path_;
PathInfoSet path_infos_;
;
FolderDiff::FolderDiff(const std::string& path)
folder_path_ = absolute(path);
PathInfoSet path_infos;
folder_scan(folder_path_,
std::bind(&FolderDiff::scan_, this, std::placeholders::_1, E_FILE, std::ref(path_infos_)),
std::bind(&FolderDiff::scan_, this, std::placeholders::_1, E_FOLDER, std::ref(path_infos_)));
void FolderDiff::scan_(const std::string& path, PathType type, PathInfoSet& path_infos)
PathInfo pipath, type;
path_infos.insert(pi);
void FolderDiff::diff(PathInfoSet & add, PathInfoSet & remove)
PathInfoSet path_infos;
folder_scan(folder_path_,
std::bind(&FolderDiff::scan_, this, std::placeholders::_1, E_FILE, std::ref(path_infos)),
std::bind(&FolderDiff::scan_, this, std::placeholders::_1, E_FOLDER, std::ref(path_infos)));
std::set_difference(path_infos.begin(), path_infos.end(),
path_infos_.begin(), path_infos_.end(), std::inserter(add, add.begin()));
std::set_difference(path_infos_.begin(), path_infos_.end(),
path_infos.begin(), path_infos.end(), std::inserter(remove, remove.begin()));
path_infos_ = path_infos;
Folder::diff方法将计算出和之前目录状态的对比结果。
FolderWatcher是最终实现文件夹监视的类。它的构造函数第8行构建了一个文件夹对比类;第10行遍历整个目录,对目录下文件夹和文件设置监视器。因为子文件夹也要监视,folder_watchers_保存了所有子文件夹的监视器。第14行启动了path路径文件夹监视器。
FolderWatcher::FolderWatcher(const std::string& path, callback c, LibevLoop* loop)
folder_path_ = absolute(path);
if (boost::filesystem::is_regular_file(folder_path_))
return;
cb_ = std::move(c);
fdiff_ = std::make_shared<FolderDiff>(folder_path_);
folder_scan(folder_path_,
std::bind(&FolderWatcher::watch_file_, this, std::placeholders::_1),
std::bind(&FolderWatcher::watch_folder_, this, std::placeholders::_1));
watcher_ = std::make_shared<Watcher>(folder_path_,
std::bind(&FolderWatcher::watch_, this,
std::placeholders::_1, std::placeholders::_2), loop);
void FolderWatcher::watch_folder_(const std::string& path)
std::unique_lock<std::mutex> lock(mutex_);
folder_watchers_[path] = std::make_shared<Watcher>(path,
std::bind(&FolderWatcher::watch_, this,
std::placeholders::_1, std::placeholders::_2));
对每个子文件的监视使用watch_file_回调,它在底层使用了之前定义的FileWatcher文件监视器类。
void FolderWatcher::watch_file_(const std::string& path)
std::unique_lock<std::mutex> lock(mutex_);
files_last_modify_time_[path] = boost::filesystem::last_write_time(path);
file_watchers_[path] = std::make_shared<FileWatcher>(path,
std::bind(&FolderWatcher::file_watcher_, this,
std::placeholders::_1, std::placeholders::_2));
void FolderWatcher::file_watcher_(const std::string& path, FileWatcherAction action)
PathInfo pipath, E_FILE;
WatcherAction ac = (WatcherAction)action;
notify_filewatcher_change_(pi, ac);
对主目录的监视使用watch_回调函数,它内部是通过之前定义的FolderDiff类实现的。
void FolderWatcher::watch_(ev::stat &w, int revents)
PathInfoSet add;
PathInfoSet remove;
fdiff_->diff(add, remove);
for (auto& it : add)
notify_change_(it, true);
for (auto& it : remove)
notify_change_(it, false);
void FolderWatcher::notify_change_(const PathInfo& pi, bool add)
if (pi.type == E_FOLDER)
notify_folderwatcher_change_(pi, add ? NEW : DEL);
else
notify_filewatcher_change_(pi, add ? NEW : DEL);
如果变动的事文件夹,则使用notify_folderwatcher_change_方法处理;如果是文件,则使用notify_filewatcher_change方法处理。
notify_folderwatcher_change_方法比较简单,它只是根据新增或者删除的行为维护folder_watchers_监视器映射表,并调用回调通知。
void FolderWatcher::notify_folderwatcher_change_(const PathInfo& pi, WatcherAction action)
bool notify = true;
if (action == DEL)
std::unique_lock<std::mutex> lock(mutex_);
auto it = folder_watchers_.find(pi.path);
if (it == folder_watchers_.end())
notify = false;
else
folder_watchers_.erase(it);
else if (action == NEW || action == MODIFY)
std::unique_lock<std::mutex> lock(mutex_);
auto it = folder_watchers_.find(pi.path);
if (it == folder_watchers_.end())
folder_watchers_[pi.path] = std::make_shared<Watcher>(pi.path,
std::bind(&FolderWatcher::watch_, this,
std::placeholders::_1, std::placeholders::_2));
if (notify)
cb_(pi, action);
notify_filewatcher_change方法比较复杂,它底层调用的change_filewatchers_方法根据文件的新增和删除来管理文件监视器。
void FolderWatcher::change_filewatchers_(const std::string& path, WatcherAction action)
if (action == DEL)
std::unique_lock<std::mutex> lock(mutex_);
auto it = file_watchers_.find(path);
if (it != file_watchers_.end())
file_watchers_.erase(it);
else if (action == NEW)
std::unique_lock<std::mutex> lock(mutex_);
auto it = file_watchers_.find(path);
if (it != file_watchers_.end())
file_watchers_.erase(it);
file_watchers_[path] = std::make_shared<FileWatcher>(path,
std::bind(&FolderWatcher::file_watcher_, this,
std::placeholders::_1, std::placeholders::_2));
由于对于文件的删除行为,文件监视器和文件夹监视器都会上报,所以需要对其进行去重。于是我们使用最后修改时间来做统一。
void FolderWatcher::notify_filewatcher_change_(const PathInfo& pi, WatcherAction action)
change_filewatchers_(pi.path, action);
bool notify = true;
if (action == DEL)
std::unique_lock<std::mutex> lock(mutex_);
auto it = files_last_modify_time_.find(pi.path);
if (it == files_last_modify_time_.end())
notify = false;
else
files_last_modify_time_.erase(it);
else if (action == NEW || action == MODIFY)
std::unique_lock<std::mutex> lock(mutex_);
auto it = files_last_modify_time_.find(pi.path);
if (it != files_last_modify_time_.end())
if (boost::filesystem::last_write_time(pi.path) == it->second)
notify = false;
else
files_last_modify_time_[pi.path] = boost::filesystem::last_write_time(pi.path);
if (notify)
cb_(pi, action);
最后需要指出的是,这套代码,在不同的操作系统上有不一致的行为。比如在Centos上,如果我们监视一个不存在的文件路径,然后新建该文件,则会发起通知。而在Ubuntu上,该行为则监视不到。但是这个差异也可以理解。
最后附上代码库,其中的单元测试基于Centos的。https://github.com/f304646673/filewatcher.git
以上是关于使用libev监视文件夹下文件(夹)属性变动的方案和实现的主要内容,如果未能解决你的问题,请参考以下文章