文件系统事件和网络IO事件一样,也可以通过epoll或者IOCP 事件管理器统一调度,当所监控的文件或文件夹发生了增删改的事件时,就会触发事件回调,进行事件处理。很常见的应用,如配置文件立即生效功能,就可以通过这种方式触发软件内部进行配置更新。也可以用来监控日志文件的更新,拉取最新日志(类似notepad++打开日志文件,如果日志文件更新了,就会提示reload)。
下面分别给出了linux和windows系统下文件系统事件监听的示意代码,仅供参考。
cpp
//file: FileMonitor.h
#ifndef INOTIFY_FILEMONITOR_H
#define INOTIFY_FILEMONITOR_H
#include <string>
#include <map>
/**
* 文件监视器。
* 用于监控文件系统中文件相关的事件,如创建、删除等操作。
*/
class FileMonitor {
public:
FileMonitor() = default;
~FileMonitor() = default;
void startWatch(const std::string &path);
void stopWatch();
bool addWatch(const std::string &path);
private:
// inotify 句柄
int inotify_fd{0};
// inotify_add_watch返回值 -> 要监控的文件名 的映射
std::map<int, std::string> watchDesc2Name;
// 是否停止监控
bool isStopped{false};
};
#endif //INOTIFY_FILEMONITOR_H
// file: FileMonitor.cpp
#include <sys/inotify.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <cstdio>
#include <iostream>
#include "FileMonitor.h"
#define log(format, ...) { \
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); \
char buf[64]; \
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); \
printf("%s | %llu | ", buf, std::this_thread::get_id()); \
printf(format, ##__VA_ARGS__); \
printf("\n"); }
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
void FileMonitor::startWatch(const std::string &path) {
log("Ready to watch...");
// inotify 初始化
inotify_fd = inotify_init();
if (!addWatch(path)) {
return;
}
// 创建一个 epoll 句柄
log("epoll_create");
int epfd = epoll_create(256);
struct epoll_event ev;
ev.data.fd = inotify_fd; // 设置要处理的事件相关的文件描述符
ev.events = EPOLLIN | EPOLLET; // 设置要处理的事件类型
// 注册 epoll 事件
log("epoll_ctl");
epoll_ctl(epfd, EPOLL_CTL_ADD, inotify_fd, &ev);
// 循环监听事件
char buffer[BUF_LEN];
struct epoll_event events[20]; // 存储从内核得到的事件集合
while (!isStopped) {
log("epoll_wait...");
// 等待事件发生。返回需要处理的事件数目
int nfds = epoll_wait(epfd, events, 20, 500);
log("Event count: %d", nfds);
for (int i = 0; i < nfds; ++i) {
/**
* epoll_wait 会一直阻塞直到下面2种情况:
* 1. 一个文件描述符触发了事件。
* 2. 被一个信号处理函数打断,或者 timeout 超时。
* 所以下面需要对 fd 进行过滤,判断是否是我们需要的 fd 产生了事件
*/
if (events[i].data.fd != inotify_fd) {
log("Other event, fd=%d", events[i].data.fd);
continue;
}
long length = read(inotify_fd, buffer, BUF_LEN);
if (length < 0) {
perror("Read failed");
}
log("Read length: %ld", length);
int pos = 0;
int count = 0;
while (pos < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[pos];
log("[%d]event->len=%u, event->mask=%#X, EVENT_SIZE=%lu", ++count, event->len, event->mask, EVENT_SIZE);
if (event->len) {
if (event->mask & IN_CREATE) {
log("CREATE: %s", event->name);
} else if (event->mask & IN_DELETE) {
log("DELETE: %s", event->name);
} else if (event->mask & IN_MODIFY) {
log("MODIFY: %s", event->name);
} else if (event->mask & IN_OPEN) {
log("OPEN: %s", event->name);
} else if (event->mask & IN_CLOSE) {
log("CLOSE: %s", event->name);
} else {
log("Unknown event, mask=%u", event->mask);
}
}
pos += EVENT_SIZE + event->len;
}
}
}
log("Stop inotify");
for (auto &ele: watchDesc2Name) {
inotify_rm_watch(inotify_fd, ele.first);
}
close(epfd);
close(inotify_fd);
}
void FileMonitor::stopWatch() {
isStopped = true;
}
bool FileMonitor::addWatch(const std::string &path) {
// TODO 需要对文件(夹)是否存在进行判断
// 监听指定目录下的修改、创建、删除事件
int wd = inotify_add_watch(inotify_fd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE);
// int wd = inotify_add_watch(inotify_fd, path.c_str(), IN_ALL_EVENTS);
if (wd < 0) {
log("inotify_add_watch failed: %s", path.c_str());
return false;
}
log("Add watch: %s", path.c_str());
watchDesc2Name[wd] = path;
return true;
}
// main: main.cpp
#include <iostream>
#include "FileMonitor.h"
int main() {
std::string dirPath = "/home/nvidia/logs";
FileMonitor fileMonitor;
fileMonitor.startWatch(dirPath);
return 0;
}
// https://github.com/BornToDeath/cpp/blob/main/test/case11_inotify/main.cpp
cpp
#include <iostream>
#include <map>
#include <windows.h>
#define DIRECTORY_PATH L"C://TestFolder"
#define CONTAINING_RECORD(address, type, field) ((type *)( \
(PCHAR)(address) - \
(ULONG_PTR)(&((type *)0)->field)))
typedef struct directory_info
{
HANDLE hDir;
CHAR szDirName[MAX_PATH];
CHAR szBuffer[4096];
DWORD dwBufLength;
OVERLAPPED Overlapped; // 此处需要定义overlapp结构
}DIRECTORY_INFO, * LPDIRECTORY_INFO;
class cFileUpdateChecker
{
public:
cFileUpdateChecker(const wchar_t* root) :hThread(NULL), dwThread(0), hComp(NULL), pdir(NULL) { wsprintf(dirPath,L"%s",root); }
~cFileUpdateChecker()
{
if (hComp)
{
PostQueuedCompletionStatus(hComp, 0, NULL, NULL);
CloseHandle(hComp);
}
if (hThread)
CloseHandle(hThread);
if (pdir)
{
CloseHandle(pdir->hDir);
HeapFree(GetProcessHeap(), 0, pdir);
}
for(auto it = opened_files.begin();it!=opened_files.end();it++)
{
if(it->second != NULL)
CloseHandle(it->second);
}
}
// 定义IOCP的工作线程
static DWORD WINAPI ThreadFunc(void* p)
{
cFileUpdateChecker* pHost = (cFileUpdateChecker*)p;
LPDIRECTORY_INFO lpdir = NULL;
DWORD nbytes, errCode;
LPOVERLAPPED lpOverlapped = NULL;
PFILE_NOTIFY_INFORMATION fni = NULL;
ULONG_PTR completion_key;
DWORD wait = 1000;
while (true)
{
BOOL bRet = GetQueuedCompletionStatus(pHost->hComp, &nbytes, &completion_key, &lpOverlapped, wait);
if (FALSE == bRet )
{
errCode = GetLastError();
if (errCode == WAIT_TIMEOUT)
{
wait = INFINITE;
continue;
}
else
break;
}
if (lpOverlapped == NULL)
{
break;
}
else
{
lpdir = CONTAINING_RECORD(lpOverlapped, DIRECTORY_INFO, Overlapped);
if (lpdir)
{
fni = (PFILE_NOTIFY_INFORMATION)lpdir->szBuffer;
int filename_length =fni->FileNameLength;
wchar_t* root_dir = (wchar_t*)lpdir->szDirName;
wchar_t* filename = &fni->FileName[0];
switch (fni->Action)
{
case FILE_ACTION_ADDED:
wprintf(L"The file %s/%s was added to the directory.\r\n", root_dir, filename);
break;
case FILE_ACTION_REMOVED:
wprintf(L"The file %s/%s was removed from the directory.\r\n", root_dir, filename);
break;
case FILE_ACTION_MODIFIED:
{
wchar_t file_path_buffer[MAX_PATH]={};
wsprintf(file_path_buffer,L"%s\\%s",root_dir, filename);
wprintf(L"The file %s was modified. This can be a change in the time stamp or attributes.\r\n", file_path_buffer);
auto it = pHost->opened_files.find(file_path_buffer);
if(it == pHost->opened_files.end())
{
HANDLE hFile = CreateFile(file_path_buffer,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile != NULL)
{
DWORD dwFilePointer = SetFilePointer(hFile,0,NULL,FILE_END);
if(dwFilePointer != INVALID_SET_FILE_POINTER)
pHost->opened_files[file_path_buffer]=hFile;
else
CloseHandle(hFile);
}
}
else
{
CHAR szReadText[50] = {};
DWORD dwRead = 0;
//需要注意的是,FILE_ACTION_MODIFIED信号仅表示文件系统中的该文件节点对象的写状态更新了,至于是否完成落盘,还需要看修改文件的进程是否完成了刷盘。由于实时性问题,刷盘动作可能存在一定的滞后,如果每次进入Modify事件都要增量读取更新的文件内容。可以增加30~50ms的延时
bRet = ReadFile(it->second,szReadText,32,&dwRead,NULL);
if(bRet == FALSE)
{
CloseHandle(it->second);
pHost->opened_files.erase(it);
}
else
{
printf("Additional file content: %s.\r\n",szReadText);
SetFilePointer(it->second,0,NULL,FILE_END);
}
}
}
break;
case FILE_ACTION_RENAMED_OLD_NAME:
{
const wchar_t* new_filename = L"";
int new_filename_length = 0;
if (fni->NextEntryOffset);
{
PFILE_NOTIFY_INFORMATION new_fni = (PFILE_NOTIFY_INFORMATION)(lpdir->szBuffer + fni->NextEntryOffset);
new_filename = &new_fni->FileName[0];
new_filename_length = new_fni->FileNameLength;
}
wprintf(L"The file %s/%s was renamed, and this is the new name %s.\r\n", root_dir, filename, new_filename);
}
break;
case FILE_ACTION_RENAMED_NEW_NAME:
wprintf(L"The file %s/%s was renamed and this is the new name.\r\n", root_dir, filename);
break;
default:
break;
}
// re post event
ZeroMemory(&(pHost->pdir->Overlapped), sizeof(OVERLAPPED));
ZeroMemory(pHost->pdir->szBuffer, 4096);
ReadDirectoryChangesW(pHost->pdir->hDir,
pHost->pdir->szBuffer,
sizeof(pHost->pdir->szBuffer),
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_LAST_WRITE,
&(pHost->pdir->dwBufLength),
(OVERLAPPED*)(&(pHost->pdir->Overlapped)),
NULL
);
}
}
}
return 0;
}
bool StartChecker()
{
hComp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
pdir = (LPDIRECTORY_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DIRECTORY_INFO));
if (NULL == pdir)
{
return false;
}
pdir->hDir = CreateFile(dirPath,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (pdir->hDir == INVALID_HANDLE_VALUE)
{
return false;
}
// 启动工作线程
hThread = CreateThread(NULL, 0, ThreadFunc, this, 0, &dwThread);
if (NULL == hThread)
{
return false;
}
ZeroMemory(&(pdir->Overlapped), sizeof(OVERLAPPED));
ZeroMemory(pdir->szBuffer, 4096);
ZeroMemory(pdir->szDirName, MAX_PATH);
wsprintf((wchar_t*)pdir->szDirName,L"%s", dirPath);
// 投递I/O请求到完成端口
HANDLE hRet = CreateIoCompletionPort(pdir->hDir, hComp,NULL, 0);
if (NULL == hComp)
{
return false;
}
BOOL bRet = ReadDirectoryChangesW(pdir->hDir,
pdir->szBuffer,
4096,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_LAST_WRITE,
&pdir->dwBufLength,
&(pdir->Overlapped),
NULL
);
if (FALSE == bRet)
{
return false;
}
return true;
}
private:
HANDLE hThread;
DWORD dwThread;
HANDLE hComp;
LPDIRECTORY_INFO pdir;
WCHAR dirPath[MAX_PATH];
std::map<std::wstring,HANDLE> opened_files;
};
int main()
{
cFileUpdateChecker* ckr = new cFileUpdateChecker(DIRECTORY_PATH);
std::cout << "Supervise "; std::wcout << DIRECTORY_PATH << " Begin..." << std::endl;
ckr->StartChecker();
getchar();
delete ckr;
getchar();
}
// https://blog.csdn.net/nhn_devlab/article/details/6034055