内容简介:我们先看个最简单方案,下面的代码会监视/home/work下文件(夹)的新增、删除等操作。第6行,我们使用了默认的loop。除了default_loop,libev还提供了dynamic_loop。如果我们没有指定loop,则libev会使用默认的。第7行,我们声明了文件(夹)监视器state。
我们先看个最简单方案,下面的代码会监视/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个问题则比较严重了。要解决第三个问题,我们需要对文件夹的监视精细到具体的文件级别,也就是说不是笼统的对某个目录进行监视,而是还要对目录下每个文件进行监视。
于是对一个文件夹的监视,需要做到:
- 监视该文件夹,以获取新增文件(夹)信息。
- 监视该文件下所有文件,以获取复制覆盖信息。
- 对于新增的文件,需要新增监视。
- 对于删除的文件,需要删除监视。
- 对于文件夹监视器和文件监视器重复上报的行为(删除文件)需要去重处理。
由于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 pi{path, 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行遍历整个目录,对目录下文件夹和文件设置监视器。由于子文件夹不用监视,所以文件夹监视函数watch_folder_实际什么都没干。第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) { }
对每个子文件的监视使用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 pi{path, 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_folderwatcher_change_(it, true); } for (auto& it : remove) { notify_folderwatcher_change_(it, false); } } void FolderWatcher::notify_folderwatcher_change_(const PathInfo& pi, bool add) { if (pi.type == E_FOLDER) { cb_(pi, add ? NEW : DEL); } else { notify_filewatcher_change_(pi, add ? NEW : DEL); } }
如果新增的文件夹,则直接调用回调函数;否则使用notify_filewatcher_change方法去通知。
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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 初学Vue(四)-- 计算属性,监视方法
- 使用BPF监视你的Kubernetes集群
- c# – 监视系统中的进程启动
- MariaDB 10.2.14 发布,新增磁盘空间监视插件
- 取色软件 ColorWanted 又更新了,新增剪贴板监视支持
- Holy Lance 1.3 发布,Linux 图形化性能监视器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。