实在不想起一个犯困的标题了,但也没想好要起什么,就这样吧,本篇主要涉及的对象是kqueue。
主要API
EV_SET
EV_SET是一个宏:
|
|
kevent结构体
代码如下:
|
|
- ident:文件描述符,比如打开一个新的文件,它的文件描述符会是3;
- filter:过滤器,可被注册监控的事件;
- flags:传入的filter事件如何设置
- EV_ADD:添加该事件到kqueue
- EV_DELETE:从kqueue删除该事件
- EV_ENABLE:该事件是否可用,默认可用
- …
- fflags:特定的过滤器flag;
- data:特定的过滤器数据值;
- opaque user data identifier;
filter & fflags
filter比较重要,需要单独列出。
filter:
- #define EVFILT_READ (-1);
- #define EVFILT_WRITE (-2);
- #define EVFILT_AIO (-3);
- #define EVFILT_VNODE (-4):检测文件改动,fflags可以具体到特定事件;
fflags:
- #define NOTE_DELETE 0x0001:文件删除;
- #define NOTE_WRITE 0x0002:文件写入;
- #define NOTE_EXTEND 0x0004:文件扩展;
- #define NOTE_ATTRIB 0x0008:属性改变;
- #define NOTE_LINK 0x0010:文件被链接的数量改变;
- #define NOTE_RENAME 0x0020:文件重命名;
- #define NOTE_EXIT 0x80000000:文件退出;
- …
kevent()
代码如下:
|
|
- kq:kqueue返回值;
- changelist & nchanges:注册/反注册事件数组;
- eventlist & nevents:向调用进程返回的事件数组;
- timeout:功能如其名,设置为NULL时,一直等待;
本人并没有找到kevent函数的源码,一切都封装好,两个数组如何使用,见后面内容。
逻辑解析
kqueue由于高度封装,模仿示例(man)就能写出代码。下面先按照本人的理解整理一下基本的逻辑思路,这里以监控文件改动为例(为了方便理解采用了面向对象的角度):
创建kqueue对象kq(由kqueue()返回)、创建一个kevent结构体ev,作用是收集参数、创建用于监控的打开文件fd。
使用宏EV_SET,把ev参数初始化,那么就需要对这些参数有了解:
- 第一个参数就是监控打开文件fd;
- 第二个参数filter就是事件;
- 如何处理filter事件?是设定入kq,还是从kq中删除?这就引入了第三个参数flag,常用的包括EV_ADD、EV_ENABLE等等;
- 有些事件需要更细的设定,比如监控改动这个事件EVFILT_VNODE,改动的方式有很多,删除(NOTE_DELETE)、写入、属性改变等等,那么也应该将“关注”的这些动作进行设定声明,这些设定需要传给第四个参数;
- 第五个参数data用于返回一些特定数据;
- 第六个udata用于携带一些信息,可以在一些情形发挥作用;
这个饱含信息量的结构体最后会进入kevent中和kq发生化学反应,什么反应就需要知道源码,前面说了我不知道,但是通常也不需要知道,只需要明白这个函数的效果就足够。
一开始看代码的时候没有弄清楚changelist和eventlist的用处,后来发现并不复杂,前者用来注册事件,后者用来实际监控注册的事件。
比如先初始化一个结构体数据:
|
|
注册就可以通过下面代码完成:
|
|
而监控的时候可以使用代码:
|
|
一开始本人困惑于eve如何初始化,不过很快就会发现,kevent函数完成了一切工作,这里eve就是之前注册的ev,在kevent()内部应该完成了一次数据的传递。
总之很奇怪的写法,但又不得不接受,这么写可能有一些基于性能的考虑,也可能,就是早起代码不规范的产物,又或者是为了隐匿具体实现,但是只要能用,就是好的。
实例
写一个最简单的实例来进行说明,这个例子主要是监控本地磁盘上readme.md文件的写事件:
|
|
当对readme.md进行写操作的时候,代码可以对该事件进行捕捉。
如果设置了timeout,那么不能取消注释。
小结
IO多路复用是Java NIO的基础,kqueue通过kevent函数对底层的实现进行了很好的封装,调用就完事。