#ifndef _WATCHER_H_ #define _WATCHER_H_ #include <sys/time.h> #include <sys/stat.h> #include <string> // We often want to know if a file was just now created or updated (then we // may reload the file to get latest data). FileWatcher does boilerplate part // for you as below. // Example: // Watcher fw; // fw.create ("path_of_file_to_be_watched"); // .... // if (fw.is_timestamp_updated() > 0) { // >0 means that the file is created or updated // ...... // fw.update_timestamp(); // update recorded timestamp // } // // You may call is_timestamp_updated() multiple times to know latest status // of the file before calling update_timestamp(), or you may combine calls to // the two functions by: // if (fw.check_and_update_timestamp() > 0) { // .... // } class Watcher { public: static const time_t NON_EXIST_TS = (time_t)-1; // must be negative static const int SKEW = -2;//文件来自未来的异常:文件的时间戳异常或者从时间较快的服务器上拷贝来的文件 static const int DELETED = -1; // must be zero static const int UNCHANGED = 0; // must be positive static const int UPDATED = 1; static const int CREATED = 2; // Default constructor explicit Watcher () : _chk_ts(NON_EXIST_TS) , _last_ts(NON_EXIST_TS)//这里的explicit没有实际意义,可以删除 {} // Create this watcher on a file // Returns: // -1 file_path is NULL // 0 success int create (const char* file_path) { if (NULL == file_path) { FATAL("file_path is NULL"); return -1; } file_path_ = file_path;//后置下划线。。。好吧。。。 struct stat tmp_st; int ret = stat (file_path_.c_str(), &tmp_st);//为了存储之前的状态 if (ret < 0) { WARN("%s does not exist", file_path_.c_str()); _chk_ts = NON_EXIST_TS; _last_ts = NON_EXIST_TS; } else { _chk_ts = tmp_st.st_mtime; _last_ts = tmp_st.st_mtime; } return 0; } // Check if watched file was created/modified/deleted. Recorded timestamp // is not changed so it's safe to call this function multiple times, you // won't miss an update. // Returns: // UPDATED the file is modified or created since last call to this // function // UNCHANGED the file is not modified or still not existing since last // call to this function // DELETED the file was deleted since last call to this function // SKEW the file exists but the timestamp steps back // Note: this method only checks, to tell file_reader_t to forget the change // (because you already processed the file), you must call // update_timestamp(). You may call check_and_update_timestamp() to // combine two calls. // Note: If the file is updated too frequently(< 500ms), this method may // return UNCHANGED due to precision of stat(2); If the file is // being updated too frequently for a period of time, this method // should eventually return several UPDATEs; If the file is being // updated and deleted too frequently, the update events are // probably undetectable. Fortunately, this is unseen in our app. int is_timestamp_updated () { struct stat tmp_st; const int ret = stat (file_path_.c_str(), &tmp_st); if (ret < 0) { _chk_ts = NON_EXIST_TS; if (NON_EXIST_TS != _last_ts) { return DELETED; } else { return UNCHANGED; } } else { _chk_ts = tmp_st.st_mtime; if (NON_EXIST_TS != _last_ts) { if (_chk_ts > _last_ts) { return UPDATED; } else if (_chk_ts == _last_ts) { return UNCHANGED; } else { return SKEW; } } else { return CREATED; } } } // Change recorded time void update_timestamp () { _last_ts = _chk_ts; } // Combine is_timestamp_updated() and update_timestamp() int check_and_update_timestamp () { const int ret = is_timestamp_updated (); update_timestamp (); return ret; } // Get path of the watched file const char* filepath () const { return file_path_.c_str(); } private: std::string file_path_; time_t _chk_ts; time_t _last_ts; }; typedef Watcher watcher_t; #endif
·代码整体比较简单,其实就是根据时间戳的各种情况做判断。
那么有了监控的程序,如果当文件更新的时候我需要动态加载该文件,怎么处理?因为加载了文件需要更新已经存储的数据,并且这些数据在动态加载的时候还可能被使用。
解决方案自然是开线程监控,使用两个buffer,再任一时刻,客户只能访问其中的一个buffer,而访问哪一个,由加载程序决定。当文件被更新时,将数据更新到没有被使用的buffer中,这样能够保证旧数据的正常访问;当更新完成,并且旧数据不再被调用的时候,通知给客户传递数据的接口使用新数据,清空之前的buffer,并将该buffer设置为未使用,等待下次更新时使用。
#ifndef _RELOADER_H #define _RELOADER_H #include <stdio.h> #include <time.h> #include <stdint.h> #include <sys/stat.h> #include <string.h> #include <new> template <bool> class static_assert; template <> class static_assert<true> {}; // @brief 双 buffer 管理类 // @note T 一定要实现 int load(const char* cp_dir, const char* cp_fname) 函数, 否则编译失败 // @note 如果有 init 需求, 可以在构造时, 提供一个 int T::init() 函数 // @note 以上两个函数在返回 < 0 时,表示失败 // @note 推荐设计:文件不存在或者内容为空,也认为是成功 template <class T> class reloader_t { public: typedef T content_type; // @param init_func,如果传入的话,reload会对new出来的对象调用 init_func // @note init_func必须是静态函数 reloader_t(const char* cp_path, const char* cp_fname, int (content_type::*init_func)() = NULL); ~reloader_t(); // @return -1, 文件不存在; 0, 不需要reload; 1, 可以reload int need_reload() const; // @brief 双 buffer 切换 // @return -1, reload失败; 1, reload成功 int reload(); // 取得当前可以使用的 buffer 的指针 content_type* get_content(); // 取得当前可以使用的 buffer 的指针 const content_type* get_content() const; time_t get_file_modtime() const { return _tm; } time_t get_file_loadtime() const { return _last_update_tm; } protected: time_t _tm; time_t _last_update_tm; uint8_t _using_no; // 两个buffer的指针 content_type* _p_content[2]; // 实际的buffer,避免reload时频繁的申请内存 char _mem[2 * sizeof(content_type)]; char _path[512]; char _fname[512]; // 个性 init 函数 int (content_type::*_init_func)(); //以下函数保证 T::load 的返回值必须为int typedef char small_t; struct big_t { char c[2]; }; static small_t _is_int(int); template <class V> static big_t _is_int(V); template <class RT, class CLA> static RT _get_load_ret_type(RT (CLA::*)(const char*, const char*)); }; template <class T> reloader_t<T>::reloader_t(const char* cp_path, const char* cp_fname, int (T::*init_func)()): _tm(0), _last_update_tm(0), _using_no(1), _init_func(init_func) { // 静态检查 load 的返回值必须是 int static_assert<sizeof(small_t) == sizeof(_is_int(_get_load_ret_type(&T::load)))>(); if (cp_path == NULL) { cp_path = "./"; } if (cp_fname == NULL) { cp_fname = "reload_file"; } snprintf(_path, sizeof(_path), "%s", cp_path); snprintf(_fname, sizeof(_fname), "%s", cp_fname); _p_content[0] = NULL; _p_content[1] = NULL; memset(&_mem, 0, sizeof(_mem)); } template <class T> reloader_t<T>::~reloader_t() { if (_p_content[0] != NULL) { _p_content[0]->~T(); _p_content[0] = NULL; } if (_p_content[1] != NULL) { _p_content[1]->~T(); _p_content[1] = NULL; } } template <class T> int reloader_t<T>::need_reload() const { char fpath[sizeof(_path) + sizeof(_fname) + 1] = ""; snprintf(fpath, sizeof(fpath), "%s/%s", _path, _fname); struct stat curr_stat; // 记录当前状态文件状态 if ((stat(fpath, &curr_stat) != 0) || ((curr_stat.st_mode & S_IFREG) == 0)) { return -1; } if (curr_stat.st_mtime <= _tm) { // 当前数据为新 return 0; } return 1; } template <class T> int reloader_t<T>::reload() { if (_using_no != 0 && _using_no != 1) { return -1; } // sleep 1 秒,保证所有线程不再使用该资源 // 外围保证 sleep(1); struct stat curr_stat; char fpath[sizeof(_path) + sizeof(_fname) + 1] = ""; snprintf(fpath, sizeof(fpath), "%s/%s", _path, _fname); // 记录当前状态文件状态 if ((stat(fpath, &curr_stat) != 0) || ((curr_stat.st_mode & S_IFREG) == 0)) { WARN("get file:%s status failed", fpath); return -1; } //保证使用的buffer编号为1或者0 int reload_no = 1 - _using_no; if (_p_content[reload_no] != NULL) { _p_content[reload_no]->~T(); _p_content[reload_no] = NULL; } _p_content[reload_no] = new (&_mem[reload_no * sizeof(T)]) T(); // init if (_init_func != NULL) { int init_ret = (_p_content[reload_no]->*_init_func)(); if (init_ret < 0) { FATAL("init failed.init ret:%d", init_ret); goto RELOAD_FAILED; } } { int load_ret = _p_content[reload_no]->load(_path, _fname); if (load_ret < 0) { FATAL("load file:%s/%s failed.load ret:%d", _path, _fname, load_ret); goto RELOAD_FAILED; } } _tm = curr_stat.st_mtime; _last_update_tm = time(NULL); _using_no = reload_no; INFO("reload [%s] success and shift to data [%d]", fpath, _using_no); return 1; RELOAD_FAILED: if (_p_content[reload_no] != NULL) { _p_content[reload_no]->~T(); _p_content[reload_no] = NULL; } return -1; } template <class T> T* reloader_t<T>::get_content() { if (_tm == 0 || (_using_no != 1 && _using_no != 0)) { return NULL; } return _p_content[_using_no]; } template <class T> const T* reloader_t<T>::get_content() const { return get_content(); } #endif该类中也实现了文件状态的判断,但是个人觉得,这种功能还是应该独立,采用组合的方式进行处理,让reloader拥有一个watcher对象是一个不错的选择。
最后,再加上线程,搞定:
#ifndef _HIT_RELOAD_H #define _HIT_RELOAD_H #include <pthread.h> #include "watcher.h" static const char WORD_RELOAD_FILE[] = "./conf/reloadWord"; class Thread {/*{{{*/ Thread(const Thread &); void operator=(const Thread &); pthread_t _thread; public: Thread() : _thread(0), _status(true), _start(false) {} virtual ~ Thread() {} //启动线程函数 int start() { int ret = 0; if (_start == false) { ret = pthread_create(&_thread, NULL, Thread::func, this); if (ret != 0) { FATAL("create thread failed."); _status = false; } else { _start = true; } } return ret; } //取得线程的状态 bool valid() const{return _status;} //取得线程句柄 pthread_t & get(){return _thread;} const pthread_t & get() const{return _thread;} //等待线程结束函数 int join(){ if (_status == true && _start == true) { return pthread_join(_thread, NULL); } return -1; } virtual void *run() = 0; protected: bool _status; bool _start; static void *func(void *args){ Thread *p_thread = static_cast <Thread *>(args); return p_thread->run(); } };/*}}}*/ class ReloadThread:public Thread {/*{{{*/ public: template<class T> void reloadCheck(Watcher *watcher, reloader_t<T> *mgr, const char* file) {/*{{{*/ bool all_reloaded = false; int ret = watcher->check_and_update_timestamp(); if (ret > 0) { all_reloaded = true; if (mgr != NULL){ if (mgr->reload() < 0) { printf("reload %s config failed", file); all_reloaded = false; } } } else if (ret < 0) { printf("watcher status not valid"); // 检测状态失败, reload未成功 all_reloaded = false; } else { // 未检测到更新 all_reloaded = false; } if (all_reloaded) { printf("%s is reloaded!", file); } }/*}}}*/ void *run(){ Watcher watcher_word; watcher_word.create("word_filter_file"); while (true) { sleep(g_conf.iSleepTime); //reload word.conf reloadCheck<WordFilter>(&watcher_word, g_data.word_filter_mgr, g_conf.word_filter_file); } return NULL; } };/*}}}*/ #endif /* vim: set ts=4 sw=4 noet: */