需求
动态监测linux系统某一个目录下文件的变化。具体使用场景如linux下应用程序运行时产生日志文件,尤其在程序出现某种异常时,日志文件记录着错误出现的原因、时间及代码位置等信息,此时日志文件在增长,但是采用轮询的方式定时查看日志文件尤为消耗性能。基于此问题,采用**“epoll+inotify异步文件监控”**的方式可以实现日志的动态刷新。
inotify
特点
inotify是一种异步文件监控机制,主要包括以下特点:
- 可监控目录或文件访问、读写、权限、删除、移动等文件系统事件;
- 监控目录时,可监控目录下的子目录或文件,但是不能递归监控子目录的目录或文件;
- 事件发生时,由内核空间主动回调至用户空间,具体为内核将事件加入inotify事件队列,用户可通过read读取;
流程
1)inotify初始化,调用inotify_init(),返回inotifyFd句柄;
2)创建epoll句柄,调用epoll_create();
3)注册epoll事件,调用epoll_ctl,将inotifyFd添加至epoll_event结构体;
4)将目录或文件添加为watch对应,调用inotify_add_watch();
5)启动线程等待epoll事件,调用epoll_wait();
6)有epoll事件发生时,调用read函数inotify事件;
7)解析inotify_event进行事件分析;
接口
epoll
- 创建epoll句柄
#include <sys/epoll.h>
int epoll_create(int size);
##参数
- size:监听的事件数目;
- 注册epoll事件
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
## 参数
- epfd:epoll_create()返回值;
- op:执行的操作。包括EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL;
- fd:文件描述符;
- event:结构体epoll_event的指针。
#### 结构体epoll_event
typedef union epoll_data
{
void *ptr; /* Pointer to user-defind data */
int fd; /* File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* epoll events(bit mask) */
epoll_data_t data; /* User data */
};
- 事件等待
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
## 参数:
- epfd:epoll_create()返回值;
- events:evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;
- maxevents:指定所evlist数组里包含的元素个数;
- timeout:确定epoll_wait()的阻塞行为。<0:一直阻塞,0:执行一次非阻塞式地检查,>0:调用将阻塞至多timeout毫秒;
inotify
- 初始化
#include <sys/inotify.h>
int inotify_init(void);
## 返回值
- 返回文件描述符
- 添加监控对象
#include <sys/inotify.h>
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
## 参数
- fd: inotify_init的返回值;
- pathname: 目录或文件路径;
- mask: 监控的事件类型;
## 返回值
- 针对pathname的watch描述符
mask事件列表:
事件 | 描述 |
---|---|
IN_ACCESS | 文件被访问 |
IN_ATTRIB | 元数据被改变,例如权限、时间戳、扩展属性、链接数、UID、GID等 |
IN_CLOSE_WRITE | 打开用于写的文件被关闭 |
IN_CREATE | 在监控的目录中创建了文件或目录 |
IN_DELETE | 在监控的目录中删除了文件或目录 |
IN_DELETE_SELF | 监控的文件或目录本身被删除 |
IN_CLOSE_NOWRITE | 不是打开用于写的文件被关闭 |
IN_MODIFY | 文件被修改 |
IN_MOVE_SELF | 监控的文件或目录本身被移动 |
IN_MOVED_FROM | 从监控的目录中移出文件 |
IN_MOVED_TO | 向监控的目录中移入文件 |
IN_OPEN | 文件或目录被打开 |
IN_ALL_EVENTS | 包含了上面提到的所有事件 |
- 移除监控对象
#include <sys/inotify.h>
int inotify_rm_watch(int fd, int wd);
## 参数值
- fd: inotify_init的返回值;
- wd: inotify_add_watch的返回值;
- inotify数据结构
struct inotify_event {
int wd; /* 监控对象的watch描述符 */
uint32_t mask; /* 事件掩码 */
uint32_t cookie; /* 和rename事件相关 */
uint32_t len; /* name字段的长度 */
char name[]; /* 监控对象的文件或目录名 */
};
示例
- 代码示例
#include <iostream>
#include <thread>
#include <sys/inotify.h>
#include <sys/epoll.h>
#include <unistd.h>
using namespace std;
#define INOTIFY_FDS 200
#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event))
#define INOTIFY_BUF_LEN (1024*(INOTIFY_EVENT_SIZE + 16))
int main()
{
// inotify初始化
int inotifyId = inotify_init();
if (-1 == inotifyId)
{
cout << "inotify_init failed" << endl;
return -1;
}
// 创建epoll句柄
int epfd = epoll_create(INOTIFY_FDS);
if (-1 == epfd)
{
cout << "epoll_create failed" << endl;
return -1;
}
// 注册epoll事件
struct epoll_event ev;
ev.data.fd = inotifyId;
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, inotifyId, &ev);
if (-1 == ret)
{
cout << "epoll_ctl failed" << endl;
return -1;
}
// 添加监听对象
const char* pathMame = "/home/inotify/dir/";
int watchFd = inotify_add_watch(inotifyId, pathMame, IN_MODIFY | IN_CREATE | IN_DELETE);
if (watchFd < 0)
{
cout << "inotify_add_watch failed" << endl;
return -1;
}
// 启动线程
std::thread func = std::thread([&]() {
// 循环监听事件
char buf[INOTIFY_BUF_LEN] = { 0, };
struct epoll_event events[20];
while (1)
{
int nfds = epoll_wait(epfd, events, 20, 1000);
for (int i = 0; i < nfds; ++i)
{
if (events[i].data.fd != inotifyId)
{
continue;
}
int length = read(inotifyId, buf, INOTIFY_BUF_LEN);
if (length < 0)
{
// error
continue;
}
int pos = 0;
while (pos < length)
{
struct inotify_event* event = (struct inotify_event*)&buf[pos];
if (event->len)
{
// 此处(event->wd == watchFd)
if (event->mask & IN_CREATE)
{
if (event->mask & IN_ISDIR)
{
// dir create
cout << "dir create" << endl;
}
else
{
// file create
cout << "file create" << endl;
}
}
else if (event->mask & IN_DELETE)
{
if (event->mask & IN_ISDIR)
{
// dir delete
cout << "dir delete" << endl;
}
else
{
// file delete
cout << "file delete" << endl;
}
}
else if (event->mask & IN_MODIFY)
{
if (event->mask & IN_ISDIR)
{
// dir modify
cout << "dir modify" << endl;
}
else
{
// file modify
cout << "file modify" << endl;
}
}
}
pos += INOTIFY_EVENT_SIZE + event->len;
}
}
// 1s更新一次
sleep(1);
}
});
func.join();
while (1)
{
// 仅作事件循环
sleep(2);
}
inotify_rm_watch(inotifyId, watchFd);
close(epfd);
close(inotifyId);
return 0;
}
- 执行结果
[root@localhost inotify]# ./testInotify
file create
dir create
file create
file modify
file create
file modify
dir delete
file delete