/首页
/开源
/关于
来,老李带你整点儿不一样的
发表@2020-11-02 13:23:08
更新@2023-04-27 20:01:06
大家好,我是神棍局副局长、小范围著名的谢顶道人 --- 老李。 作为众多打工人中的一员,老李每天早上醒来都是奄奄一息的,那么,怎么着才能打满鸡血变成元气满满的一天呢?当然是拍手舞了,那么拍手舞怎么跳呢?贴心老李自然还要再送你一个在线拍手舞教程:
最近总是看到有泥腿子在抱怨啊,说什么「面试造火箭,进厂拧螺丝」,要么就是「一天天复制粘贴CURD」,这是两个核心问题: - 一天天CURD真没劲,感觉是塑料程序员、32K真码农 - 为毛面试都是长征火箭级别的,真能进去后还是复制粘贴CURD 「过度CURD以后,腰腿酸痛,精神不振,感觉身体被掏空。是不是被透支了?怕再也不能给CURD稳稳的幸福... ...」 「他来了!他来了!他脚踏祥云过来了!今天,油腻老李,谢顶道人,在线解惑!三十年老军医,专治不...」 「...你好,CURD也好」 谢顶道人将通过一个小系列带各路神棍、泥腿子体验一把飞一般的感觉:LogAgent蛋生记。先不说LogAgent是干啥的,今天先入门我先提出一个技术场景问题:如何高效地监控文件发生变动? 高不高效不知道,反正各路神棍们不约而同地说:「先打开文件,然后while true不断地怼就是了,就跟打桩机似的、就跟电动小马达似的,不停...」,一小部分泥腿子思路或者感觉大概是有的,说「类似于IO多路复用那种方式」,不过这一小部分泥腿子很快、迅速地就被大部分坚持「能用就行」的泥腿子给干翻剿灭了:  实际上,早很很久之前的公元2005年,当Linux Kernel 2.6.13发布的时候,文件系统中就集成了一个叫做inotify的组件,这个玩意的作者分别是John、Amy和Robot(排名不分先后)。inotify出现的目的是为了代替Linux Kernel中的dnotify(由于历史比较久远老李本身也没有碰过dnotify)。据史料记载这个inotify具备如下几个优点: - 避免了while true这种人肉打桩机、电动小马达形式的主动轮训方案 - 避免像dnotify那种「被监控文件或者目录」不得不都要创建fd的浪费 - 提升监控细粒度,除了目录外,还可以监控具体文件,而据史料记载dnotify只能监控目录(注意是据史料记载) - inotify还可以直接利用select、poll这种IO多路复用,非常方便 而它的API就三个,非常粗暴,你们感受一下: - int inotify_init(void) - int inotify_add_watch(int fd, const char *pathname, uint32_t mask); - int inotify_rm_watch(int fd, int wd); 简单说明一下inotify API的使用流程: - 第一步利用inotify_init()初始化返回一个fd - 然后将该fd与「要监控的文件或者目录路径」与「要监控的文件改变类型」三个参数一同放到inotify_add_watch()中,该函数返回一个wd(请理解为watch描述符) - 最后一步并不是inotify_rm_watch()而是利用read()读取事件流然后对事件进行判断分析即可,其实就是一大坨if else针对不同的改变类型做出不同的响应来。inotify会将文件/文件夹上发生的事件(本质上是个inotify_event结构体)按照顺序存储起来,利用read()读取第一步里的文件描述,事件会被存储到read()的函数的buffer中去 只有三个API,想必代码一定很好写了(不看注释,损失三个亿,加上你在厕所已经损失的那三个亿,一共六亿): ```PHP #include
#include
#include
#include
#define BUF_SIZE 100000 int main(int argc, char **argv) { int inotify_fd; int inotify_watch_fd; int read_buf_length; char * file_path = "./api.log"; char buffer[BUF_SIZE]; char * p; struct inotify_event * single_inotify_event; // 第一步:init一下咯...bzero()是为了清空一下内存,保证无脏数据 bzero(buffer, BUF_SIZE); inotify_fd = inotify_init(); if (-1 == inotify_fd) { printf("inotify-init-error"); return -1; } // 第二步:添加watch,将要监控的文件或者目录搞进来,最后一个参数是要监控的事件类型 /* 注意最后一个参数,是一个mask。他有如下几个数值: IN_OPEN 就是打开文件被监控到了 IN_CLOSE_WRITE 打开文件、写文件、关闭 IN_CLOSE_NOWRITE 打开文件,看了看,啥也没干关闭了 IN_MODIFY 文件被改动了 IN_DELETE 被unlink删除了... 太多了,所有情况看下面程序demo */ inotify_watch_fd = inotify_add_watch(inotify_fd, file_path, IN_ALL_EVENTS); if (-1 == inotify_watch_fd) { printf("inotify-watch-error"); return -1; } // 第三步:loop起来。有人会说:你这个loop不也是打桩机吗? // 但是这个打桩机至少是半自动打桩机。它只有文件状态真的发生变化时候才会打桩 // 一句话:老打桩机打桩是为了发现文件状态变化,新打桩机是发了变化后才会打桩 while(1) { //printf("in while-loop pre\r\n"); // 默认情况下,read会被阻塞起来,一直到从inotify-fd中读取到变化并存储d到buffer中去 // 读取到的内容:就是下面event结构体,可能会有好几个 /* struct inotify_event { int wd; // watch文件描述符 uint32_t mask; // 所发生变化的mask数值 uint32_t cookie; // 据文档说当下只有监控目录,发生move-from和move-to的时候,用于串联事件使用,其他概况一般默认都是0 uint32_t len; // name的长度 char name[]; // 只有监控目录变化时候,目录中新增文件等name就会有数值了 }; */ read_buf_length = read(inotify_fd, buffer, BUF_SIZE); //printf("in while-loop | read_length = %d\r\n", read_buf_length); if (-1 == read_buf_length) { printf("read-error"); continue; } for (p = buffer; p < buffer+read_buf_length;) { single_inotify_event = (struct inotify_event *)p; printf("wd = %2d, name=%s\r\n", single_inotify_event->wd, single_inotify_event->name); /* 截止到目前为止,还有很多人不明白这种用mask实现类似于开关或者 配置的好处和优势,包括原理... */ if (single_inotify_event->mask & IN_ACCESS) { printf("in-access\r\n"); } if (single_inotify_event->mask & IN_ATTRIB) { printf("in-attrib\r\n"); } if (single_inotify_event->mask & IN_CREATE) { printf("in-create\r\n"); } if (single_inotify_event->mask & IN_MODIFY) { printf("in-modify\r\n"); } if (single_inotify_event->mask & IN_OPEN) { printf("in-open\r\n"); } if (single_inotify_event->mask & IN_CLOSE_WRITE) { printf("in-close-write\r\n"); } if (single_inotify_event->mask & IN_CLOSE_NOWRITE) { printf("in-close-nowrite\r\n"); } if (single_inotify_event->mask & IN_MOVE_SELF) { printf("in-move-self\r\n"); } if (single_inotify_event->mask & IN_MOVED_FROM) { printf("in-moved-from\r\n"); } if (single_inotify_event->mask & IN_MOVED_TO) { printf("in-move-to\r\n"); } if (single_inotify_event->mask & IN_IGNORED) { printf("in-IGNORED\r\n"); } if (single_inotify_event->mask & IN_DELETE) { printf("in-delete\r\n"); } if (single_inotify_event->mask & IN_DELETE_SELF) { printf("in-delete-self\r\n"); } /* 下面这行有个难点需要注意:就是buffer中这一连串的inotify_event,读第一个后,如何读出第二个? 界定一个inotify_event结构体的长度是:sizeof(struct inotify_event) + single_inotify_event->len 不要忘记了inotify_event结构体中有一个成员是name,是char[],他的长度用另一个成员len可以获取到 所以,这就是在buffer中界定一个inotfiy_event边界的办法 能界定边界了,程序就可以读完buffer中所有的event了 那么问题又来了,这个buffer应该定成多长呢?因为name长度是不定长的,所以有一种鸡贼的办法: (sizeof(struct inotify_event) + NAME_MAX + 1) * 事件数量(最多十来个) 结构体本身c长度 加上 NAME_MAX常量(表示文件名最大长度),再加上1是末尾的'\0'长度 由于一个文件上所能发生的inotify事件数量是有上限的,所以不要手软,直接写s上限最大数值 所以这样的buffer,是一定足够盛放所有event结构体了 */ p += (sizeof(struct inotify_event) + single_inotify_event->len); } printf("--- one-loop-end ---\r\n\r\n\r\n"); } } ``` gcc搞一下跑个测试吧,注意记得同级别目录下创建好api.log文本文件。大概如下图所示,你们感受一下:  我建议大伙儿把所有事件都尝试一下,上面是对某个文件的监控,你一定要再试试文件夹的。除此之外提醒一点儿:可能会有人用vim对api.log进行编辑,但是除了vim打开文件时候能看到效果,你写内容都不会看到效果,原因是啥?自己思考思考。 那事情到这儿就有泥腿子要问了:你这个用C写的demo,直接对接Linux API,我就一个PHP泥腿子,连Go也不会,我能咋办?不,腿子,听我说,PHP也可以办。心有多宽广,舞台就有多大!只要你想干!PHP都能写LogAgent! 首先下载并安装PHP版本的inotify扩展(我假装你们都会能搞定),然后复制粘贴下面的demo: ```php