1 Overview
   
最近公司的一个任务需要实时监控文件系统中某个文件的内容变化。由于程序本身由Java编写,因此使用了inotify-
java(http://code.google.com/p/inotify-java/)。inotify-java只是对Linux中
inotify相关的内核调用进行了封装,因此在使用inotify-java之前有必要了解一下inotify。
    
inotify是一种基于inode的文件系统监控机制。从2.6.13-rc3版本起被集成到Linux 内核中,作为dnotify的替代。跟dnotify相比,inotify除了更易于使用之外还有以下主要的优点:

  • inotify使用异步的事件通知机制。
  • 可以监控文件系统中的任何对象(dnotify只能监控目录)。如果inotify监控的是目录,那么在目录中的某个文件发生变化时,inotify可以通知发生变化的文件名(dnotify只能报告有变化,应用程序本身需要判断是哪个文件发生变化)。
  • 对于每个被监控的文件,inotify不需要维护一个打开的文件描述符,因此不会影响unmount之类的操作,相反会在被监控文件所在的文件系统被unmount时得到一个通知。

2 inotify
2.1 interfaces


    如果C库支持inotify,那么在C程序中直接#include <sys/inotify.h> 即可。
    通过int inotify_init
(void)系统调用进行初始化。返回值小于0说明调用失败;否则会在内核中创建一个inotify实例,并且返回对应的文件描述符。该文件描述符用于读
取inotify事件(inotify
event),读取的方式既可以是阻塞式,例如read,也可以是非阻塞式,例如select,poll和epoll(java6已经支持epoll,但
是不支持对FileChannel进行select)。
    通过int inotify_add_watch (int fd, const char *path, __u32
mask)系统调用添加监控(watch)。参数fd是inotify_init调用返回的文件描述符;参数path是监控对象的路径(文件,目录等);
参数mask是期望得到通知的事件类型的位掩码。inotify可以监控以下类型的事件:opens、closes、reads、writes、
creates、deletes、moves、metadata changes 和
unmounts。可以向一个inotify实例添加多个监控。该系统调用在成功情况下返回一个监控描述符(watch
descriptor),它被用来标识不同的监控。mask参数的可选值如下:

Event Description
IN_ACCESS File was read from.
IN_MODIFY File was written to.
IN_ATTRIB File's metadata (inode or xattr) was changed.
IN_CLOSE_WRITE File was closed (and was open for writing).
IN_CLOSE_NOWRITE File was closed (and was not open for writing).
IN_OPEN File was opened.
IN_MOVED_FROM File was moved away from watch.
IN_MOVED_TO File was moved to watch.
IN_DELETE File was deleted.
IN_DELETE_SELF The watch itself was deleted.
IN_CLOSE IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
IN_MOVE IN_MOVED_FROM | IN_MOVED_TO
IN_ALL_EVENTS Bitwise OR of all events.
IN_ONESHOT One shot support

假设希望监控/home/user1/data.txt文件的读取和修改事件,那么可以使用IN_ACCESS和IN_MODIFY,例如:

  1. int wd;
  2. wd = inotify_add_watch (fd, "/home/user1/data.txt", IN_ACCESS | IN_MODIFY);

假设希望只监控/home/user1/data.txt文件的修改事件一次,那么可以使用IN_MODIFY 和IN_ONESHOT,例如:

  1. int wd;
  2. wd = inotify_add_watch (fd, "/home/user1/data.txt", IN_MODIFY | IN_ONESHOT);

通过int inotify_rm_watch (int fd, int wd)系统调用移除监控。参数fd是inotify_init调用返回的文件描述符;参数wd是要被移除的监控描述符。如果调用成功,那么返回0;否则返回负值。
    通过int close (int fd)系统调用销毁inotify实例,以及关联的所有监控和未决事件。参数fd是inotify_init调用返回的文件描述符。

2.2 configuration

    inotify可以通过procfs和sysctl进行配置。/proc/sys/fs/inotify/目录下有以下三个文件:

  • max_queued_events 最大排队的事件个数。如果排队事件个数达到此值,那么新到的时间会被丢弃,并发送IN_Q_OVERFLOW事件。默认值16,384。
  • max_user_instances 每个用户可以创建的inotify实例最大值。默认值128。
  • max_user_watches 每个用户可以创建的监控的最大值。默认值8,192。

2.3 notifications

    inotify的事件是异步通知的,并且在内部进行了排队。但是对事件的读取必须以同步方式进行。如果以read读取,那么该方法一直阻塞到有事件到达,并且一次会读入所有排队中的事件。inotify的通知事件由inotify_event结构体定义,如下:

  1. struct inotify_event {
  2. __s32 wd;             /* watch descriptor */
  3. __u32 mask;           /* watch mask */
  4. __u32 cookie;         /* cookie to synchronize two events */
  5. __u32 len;            /* length (including nulls) of name */
  6. char name[0];        /* stub for possible name */
  7. };

其中wd是监控描述符,跟调用inotify_add_watch()时返回的监控描述符对应。如果应用程序想知道与之对应的文件,那么应用程序本身需要
建立监控描述符和文件之间的对应关系;mask是事件的位掩码;cookie用于关联两个独立的事件(例如IN_MOVED_FROM和
IN_MOVED_TO事件);len是name的长度;name是发生事件的对象名,如果监控的是目录,那么name是发生事件的文件名。如果监控的是
文件,那么name是null。
   
如果被监控的目录或者文件被unmount卸载,那么inotify会发送IN_UNMOUNT。假设监控对象是个目录,如果将被监控目录中某个文件移动
到被监控目录外,那么inotify会发送IN_MOVED_FROM事件;如果将被监控目录外的某个文件移动到被监控目录中,那么inotify会发送
IN_MOVED_TO;如果将被监控目录中的某个文件改名(即移动到相同目录中),那么inotify会发送IN_MOVED_FROM和
IN_MOVED_TO两个事件,并且这两个事件的cookie相同。
   
需要注意的是,如果使用vi对被监控的文件进行编辑,那么不会得到IN_MODIFY事件,而是会得到IN_DELETE_SELF、
IN_MOVE_SELF和IN_IGNORED三个事件。这是因为vi其实是在一个副本上进行编辑,保存的时候将原文件覆盖。收到IN_IGNORED
事件说明监控已经自动地从inotify实例中移除(由于监控对象已经被删除,或者所在的文件系统被unmount)。由于监控已被移除,所以
inotify实例以后也不会再发送此文件相关的任何事件。

3 inotify-java

   
inotify-java并不复杂,每个Inotify实例都会创建两个线程:readerThread和queueThread。
readerThread在循环中调用native
read方法接收事件,并将接收到的事件放入BlockingQueue中。queueThread从BlockingQueue中take事件,回调
InotifyEventListener。
   
inotify-java使用起来也比较简单。首先需要实例化inotify对象(在其构造函数中会调用
System.loadLibrary("inotify-java"));然后在inotify对象上注册InotifyEventListener;
最后通过inotify对象的addWatch方法添加监控即可。以下是段示例代码:

    1. import java.util.HashMap;
    2. import java.util.Map;
    3. import com.den_4.inotify_java.Constants;
    4. import com.den_4.inotify_java.EventQueueFull;
    5. import com.den_4.inotify_java.Inotify;
    6. import com.den_4.inotify_java.InotifyEvent;
    7. import com.den_4.inotify_java.InotifyEventListener;
    8. public class Test {
    9. //
    10. private static final Map<Integer, String> MASKS = new HashMap<Integer, String>();
    11. static {
    12. MASKS.put(Constants.IN_ACCESS, "IN_ACCESS");
    13. MASKS.put(Constants.IN_MODIFY, "IN_MODIFY");
    14. MASKS.put(Constants.IN_ATTRIB, "IN_ATTRIB");
    15. MASKS.put(Constants.IN_CLOSE_WRITE, "IN_CLOSE_WRITE");
    16. MASKS.put(Constants.IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE");
    17. MASKS.put(Constants.IN_OPEN, "IN_OPEN");
    18. MASKS.put(Constants.IN_MOVED_FROM, "IN_MOVED_FROM");
    19. MASKS.put(Constants.IN_MOVED_TO, "IN_MOVED_TO");
    20. MASKS.put(Constants.IN_CREATE, "IN_CREATE");
    21. MASKS.put(Constants.IN_DELETE, "IN_DELETE");
    22. MASKS.put(Constants.IN_DELETE_SELF, "IN_DELETE_SELF");
    23. MASKS.put(Constants.IN_MOVE_SELF, "IN_MOVE_SELF");
    24. MASKS.put(Constants.IN_UNMOUNT, "IN_UNMOUNT");
    25. MASKS.put(Constants.IN_Q_OVERFLOW, "IN_Q_OVERFLOW");
    26. MASKS.put(Constants.IN_IGNORED, "IN_IGNORED");
    27. MASKS.put(Constants.IN_ONLYDIR, "IN_ONLYDIR");
    28. MASKS.put(Constants.IN_DONT_FOLLOW, "IN_DONT_FOLLOW");
    29. MASKS.put(Constants.IN_MASK_ADD, "IN_MASK_ADD");
    30. MASKS.put(Constants.IN_ISDIR, "IN_ISDIR");
    31. MASKS.put(Constants.IN_ONESHOT, "IN_ONESHOT");
    32. }
    33. public static void main(String args[]) {
    34. try {
    35. Inotify i = new Inotify();
    36. InotifyEventListener e = new InotifyEventListener() {
    37. public void filesystemEventOccurred(InotifyEvent e) {
    38. System.out.println("inotify event, mask: " + getMask(e.getMask()) + ", name: " + e.getName() + ", now: " + System.currentTimeMillis());
    39. }
    40. public void queueFull(EventQueueFull e) {
    41. System.out.println("inotify event queue: " + e.getSource() +  " is full");
    42. }
    43. };
    44. i.addInotifyEventListener(e);
    45. i.addWatch("./test/", Constants.IN_MODIFY);
    46. } catch (Throwable e) {
    47. e.printStackTrace();
    48. }
    49. }
    50. public static String getMask(int mask) {
    51. StringBuilder sb = new StringBuilder();
    52. for(Integer m : MASKS.keySet()) {
    53. if((mask & m) != 0) {
    54. if(sb.length() > 0) {
    55. sb.append("|");
    56. }
    57. sb.append(MASKS.get(m));
    58. }
    59. }
    60. return sb.toString();
    61. }
    62. }

inotify-java linux系统监听文件发生变化,实时通知java程序的更多相关文章

  1. linux系统使用sh文件传参数给matlab程序

      linux系统下使用sh文件传参数给matlab程序 (1)编写sh文件 程序以下面的行开始(必须在文件的第一行):   #!/bin/sh 定义需要传递的参数,用双引号引起,参数之间使用逗号或分 ...

  2. java 监听文件或文件夹变化

    今天遇到一个新需求,当从服务器下载文件后用指定的本地程序打开,不知道何时文件下载完成,只能考虑监听文件夹,当有新文件创建的时候打开指定程序. 在此给出一个完整的下载和打开过程: 1.下载文件 jsp页 ...

  3. Java NIO.2 使用Path接口来监听文件、文件夹变化

    Java7对NIO进行了大的改进,新增了许多功能: 对文件系统的访问提供了全面的支持 提供了基于异步Channel的IO 这些新增的IO功能简称为 NIO.2,依然在java.nio包下. 早期的Ja ...

  4. 【转载】java 监听文件或者文件夹变化的几种方式

    1.log4j的实现的文件内容变化监听 package com.jp.filemonitor; import org.apache.log4j.helpers.FileWatchdog; public ...

  5. inotify监听文件

    inotify监听文件并通知 static int inotify_dbfile(const char *spFromRule, const char *spDevFile) { int inotif ...

  6. node.js监听文件变化

    前言 随着前端技术的飞速发展,前端开发也从原始的刀耕火种,向着工程化效率化的方向发展.在各种开发框架之外,打包编译等技术也是层出不穷,开发体验也是越来越好.例如HMR,让我们的更新可以即时可见,告别了 ...

  7. 【Oracle】环境变量与监听文件

    一.环境变量的及其含义: 数据库主目录 ORACLE_HOME=D:\app\Administrator\product\11.2.0\dbhome_1 监听文件所在目录 TNS_ADMIN=D:\a ...

  8. SpringMVC 监听文件上传进度

    Spring MVC 监听文件上传进度 具体实现分三个步骤: 接管CommonsMultipartResolver,重写针对文件上传的请求. 在第一步中写入监听,以获取上传进度. 修改上传部分的配置文 ...

  9. 利用WatchService监听文件变化

    在实现配置中心的多种方案中,有基于JDK7+的WatchService方法,其在单机应用中还是挺有实践的意义的. 代码如下: package com.longge.mytest; import jav ...

随机推荐

  1. delete、truncate与drop的区别

    转自:SQL truncate .delete与drop区别 相同点: 1.truncate和不带where子句的delete.以及drop都会删除表内的数据. 2.drop.truncate都是DD ...

  2. JVM调优总结-调优方法

    JVM调优工具 Jconsole,jProfile,VisualVM Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用.对垃圾回收算法有很详细的跟踪 JProfiler ...

  3. mib.c

    函数 sprint_realloc_by_type  (E:\code\net-snmp-5.4.2.1\snmplib\mib.c)的主要作用是: 解析传入的mib中的节点,根据mib节点类型,将其 ...

  4. knockout.js

    最近在使用knockout这个JS的MVVM模型,真的很不错,每次去查英文的文档,的确很累的,抽空的时候就把看到的文档按自己的理解翻译一下.当然我不是逐字的翻译. knockout的官方说明:http ...

  5. ecshop订单状态对应值详解

    ecshop的订单状态都是在ecs_order_info表中的字段里. 订单状态 未确认 取消 确认 已付款 配货中 已发货 已收货 退货 order_status 0 2 1 1 1 5 5 4 s ...

  6. BZOJ3036绿豆蛙的归宿

    BZOJ3036绿豆蛙的归宿 锲下陟凝 褰宓万 郝瓦痕膳 叶诙摞 А知π剧 椐猊∫距 屠缲佗 ゲ蕖揪 俜欧彖鹤 磲砩ほ #琛扶 觅电闸ス 捆鳢げ 浜窠 魂睨"烁 蕞滗浼 洒ヂ跪 ...

  7. zookeeper集群实例

    zookeeper是什么 Zookeeper,一种分布式应用的协作服务,是Google的Chubby一个开源的实现,是Hadoop的分布式协调服务,它包含一个简单的原语集,应用于分布式应用的协作服务, ...

  8. Unable to chmod

    不能改变权限 Unable to chmod /system/build.prop.: Read-only file system 解决方式: before chmod: Code: mount -o ...

  9. LINQ&EF in 用算的写法 like

  10. AVFoundation(一)---AVAudioPlayer

    AVAudioPlayer相当于一个播放器,它支持多种音频格式,而且能够进行进度.音量.播放速度等控制. 下边通过代码来看一下,它的属性和常用方法(具体说明都写在了注释中): //AVAudioPla ...