php使用inotify擴展監控文件或目錄的變化

1、安裝inotify擴展php

一、下載inotify擴展源碼數組

https://pecl.php.net/package/inotify

對於php7以上版本,請下載 inotify-2.0.0.tgz。安全

二、編譯安裝cookie

tar xf inotify-2.0.0.tgz
cd inotify-2.0.0
/data/nmp/php/bin/phpize
./configure --with-php-config=/data/nmp/php/bin/php-config
make && make install

三、修改php.ini,添加php7

extension = inotify.so

  

2、使用inotify進行文件監控函數

php的inotify擴展提供了監控文件或目錄的功能,能夠用來實現,服務的熱更新,或安全監控。this

inotify一共提供了5個函數:.net

一、inotify_init() 用於初始化一個實例,返回的是一個 stream 資源對象,能夠被標準stream函數使用,如stream_set_blocking()。code

二、inotify_add_watch() 將一個文件或目錄添加到監控列表中,若是存在,則替換。對象

三、inotify_read() 從 inotify 實例中讀取事件,若是沒有返回false,若是有,返回一個 inotify 事件數組。

四、inotify_queue_len() 返回隊列中事件的個數,能夠理解爲系統把 inotify_add_watch 關注的事件一個個加入隊列,經過 inotify_read() 讀取事件。

五、inotify_rm_watch() 取消監控。

一個簡單的阻塞監控代碼:

<?php

//初始化一個inotify實例
$fd = inotify_init();

//設置爲阻塞模式,當inotify_read()沒法從$fd中讀取到事件時,會一直阻塞在那裏。
stream_set_blocking($fd, 1);

//事件掩碼
$eventMask = [
    IN_ACCESS => 'File was accessed (read)',
    IN_MODIFY => 'File was modified',
    IN_ATTRIB => 'Metadata changed',
    IN_CLOSE_WRITE => 'File opened for writing was closed',
    IN_CLOSE_NOWRITE => 'File not opened for writing was closed',
    IN_OPEN => 'File was opened',
    IN_MOVED_TO => 'File moved into watched directory',
    IN_MOVED_FROM => 'File moved out of watched directory',
    IN_CREATE => 'File or directory created in watched directory',
    IN_DELETE => 'File or directory deleted in watched directory',
    IN_DELETE_SELF => 'Watched file or directory was deleted',
    IN_MOVE_SELF => 'Watch file or directory was moved',
    IN_CLOSE => 'Equals to IN_CLOSE_WRITE | IN_CLOSE_NOWRITE',
    IN_MOVE => 'Equals to IN_MOVED_FROM | IN_MOVED_TO',
    IN_ALL_EVENTS => 'Bitmask of all the above constants',
    IN_UNMOUNT => 'File system containing watched object was unmounted',
    IN_Q_OVERFLOW => 'Event queue overflowed (wd is -1 for this event)',
    IN_IGNORED => 'Watch was removed (explicitly by inotify_rm_watch() or because file was removed or filesystem unmounted',
    IN_ISDIR => 'Subject of this event is a directory',
    IN_ONLYDIR => 'Only watch pathname if it is a directory',
    IN_DONT_FOLLOW => 'Do not dereference pathname if it is a symlink',
    IN_MASK_ADD => 'Add events to watch mask for this pathname if it already exists',
    IN_ONESHOT => 'Monitor pathname for one event, then remove from watch list.',
    1073741840 => 'High-bit: File not opened for writing was closed',
    1073741856 => 'High-bit: File was opened',
    1073742080 => 'High-bit: File or directory created in watched directory',
    1073742336 => 'High-bit: File or directory deleted in watched directory',
];

//添加監控,修改,建立,刪除事件
$watch = inotify_add_watch($fd, './tmp', IN_MODIFY | IN_CREATE | IN_DELETE | IN_ISDIR);

//循環讀取事件
while ($events = inotify_read($fd)) {
    // $events會是一個二維數組
    // wd 表示inotify_add_watch()返回的監控描述符
    // mask 表示事件掩碼
    // cookie 表示惟一的ID,用於關聯相關的事件
    // name 表示文件的名稱
    echo date('Y-m-d H:i:s'), PHP_EOL;
    foreach ($events as $event) {
        $eventName = isset($eventMask[$event['mask']]) ? $eventMask[$event['mask']] : '';
        echo $event['name'], PHP_EOL;
        echo $eventName, PHP_EOL;
    }
}

//取消監控
inotify_rm_watch($fd, $watch);

//關閉資源
fclose($fd);

  

3、咱們經過非阻塞的方式來監控文件

<?php

//初始化一個inotify實例
$fd = inotify_init();

//設置爲非阻塞模式
stream_set_blocking($fd, 0);

//事件掩碼
$eventMask = [
    IN_ACCESS => 'File was accessed (read)',
    IN_MODIFY => 'File was modified',
    IN_ATTRIB => 'Metadata changed',
    IN_CLOSE_WRITE => 'File opened for writing was closed',
    IN_CLOSE_NOWRITE => 'File not opened for writing was closed',
    IN_OPEN => 'File was opened',
    IN_MOVED_TO => 'File moved into watched directory',
    IN_MOVED_FROM => 'File moved out of watched directory',
    IN_CREATE => 'File or directory created in watched directory',
    IN_DELETE => 'File or directory deleted in watched directory',
    IN_DELETE_SELF => 'Watched file or directory was deleted',
    IN_MOVE_SELF => 'Watch file or directory was moved',
    IN_CLOSE => 'Equals to IN_CLOSE_WRITE | IN_CLOSE_NOWRITE',
    IN_MOVE => 'Equals to IN_MOVED_FROM | IN_MOVED_TO',
    IN_ALL_EVENTS => 'Bitmask of all the above constants',
    IN_UNMOUNT => 'File system containing watched object was unmounted',
    IN_Q_OVERFLOW => 'Event queue overflowed (wd is -1 for this event)',
    IN_IGNORED => 'Watch was removed (explicitly by inotify_rm_watch() or because file was removed or filesystem unmounted',
    IN_ISDIR => 'Subject of this event is a directory',
    IN_ONLYDIR => 'Only watch pathname if it is a directory',
    IN_DONT_FOLLOW => 'Do not dereference pathname if it is a symlink',
    IN_MASK_ADD => 'Add events to watch mask for this pathname if it already exists',
    IN_ONESHOT => 'Monitor pathname for one event, then remove from watch list.',
    1073741840 => 'High-bit: File not opened for writing was closed',
    1073741856 => 'High-bit: File was opened',
    1073742080 => 'High-bit: File or directory created in watched directory',
    1073742336 => 'High-bit: File or directory deleted in watched directory',
];

//添加監控,修改,建立,刪除事件
$watch = inotify_add_watch($fd, './tmp', IN_MODIFY | IN_CREATE | IN_DELETE | IN_ISDIR);


//循環
while (true) {
    //全部可讀的流,監聽讀事件
    $reads = [$fd];
    //可寫的流,設爲空,表示咱們不須要監聽寫事件
    $write = [];
    //異常事件
    $except = [];
    //參數四,表示超時時間
    //若是設置成大於0,表示等待事件發生的超時時間
    //若是設置等於0,表示不斷調用select,執行後立馬返回
    //若是設置null,表示阻塞一直到監聽發生變化
    if (stream_select($reads, $write, $except, 3) > 0) {
        //注意$reads是引用傳遞,select每次會把可讀的流放到$reads中,因此$reads變量是會改變的
        if (!empty($reads)) {
            foreach ($reads as $read) {
                //從可讀流中讀取數據
                $events = inotify_read($read);
                foreach ($events as $event) {
                    echo $event['name'], ' --- ', $eventMask[$event['mask']], PHP_EOL;
                }
            }
        }
    } else {
        echo 'select timeout ...', PHP_EOL;
    }
}

//取消監控
inotify_rm_watch($fd, $watch);

//關閉資源
fclose($fd);

  

4、封裝一個類,用來監控文件或目錄。

<?php

class InotifyMonitor
{
    //須要監控的事件
    const MONITOR_EVENT = IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_CLOSE_WRITE;

    //事件掩碼
    const EVENT_MASK = [
        IN_ACCESS => 'File was accessed (read)',
        IN_MODIFY => 'File was modified',
        IN_ATTRIB => 'Metadata changed',
        IN_CLOSE_WRITE => 'File opened for writing was closed',
        IN_CLOSE_NOWRITE => 'File not opened for writing was closed',
        IN_OPEN => 'File was opened',
        IN_MOVED_TO => 'File moved into watched directory',
        IN_MOVED_FROM => 'File moved out of watched directory',
        IN_CREATE => 'File or directory created in watched directory',
        IN_DELETE => 'File or directory deleted in watched directory',
        IN_DELETE_SELF => 'Watched file or directory was deleted',
        IN_MOVE_SELF => 'Watch file or directory was moved',
        IN_CLOSE => 'Equals to IN_CLOSE_WRITE | IN_CLOSE_NOWRITE',
        IN_MOVE => 'Equals to IN_MOVED_FROM | IN_MOVED_TO',
        IN_ALL_EVENTS => 'Bitmask of all the above constants',
        IN_UNMOUNT => 'File system containing watched object was unmounted',
        IN_Q_OVERFLOW => 'Event queue overflowed (wd is -1 for this event)',
        IN_IGNORED => 'Watch was removed (explicitly by inotify_rm_watch() or because file was removed or filesystem unmounted',
        IN_ISDIR => 'Subject of this event is a directory',
        IN_ONLYDIR => 'Only watch pathname if it is a directory',
        IN_DONT_FOLLOW => 'Do not dereference pathname if it is a symlink',
        IN_MASK_ADD => 'Add events to watch mask for this pathname if it already exists',
        IN_ONESHOT => 'Monitor pathname for one event, then remove from watch list.',
        1073741840 => 'High-bit: File not opened for writing was closed',
        1073741856 => 'High-bit: File was opened',
        1073742080 => 'High-bit: File or directory created in watched directory',
        1073742336 => 'High-bit: File or directory deleted in watched directory',
    ];

    //用於保存inotify_init返回的資源
    public $fds = [];
    //用於保存監控的文件路徑
    public $paths = [];
    //用於保存inotify_add_watch返回的監控描述符
    public $wds = [];
    //超時時間
    public $timeout = 3;

    /**
     * $paths添加監控的路徑數組,能夠是目錄或文件
     */
    public function __construct($paths = [])
    {
        if (!empty($paths)) {
            foreach ($paths as $path) {
                if (file_exists($path)) {
                    if (is_dir($path)) {
                        $this->addDir($path);
                    } else {
                        $this->addFile($path);
                    }
                }
            }
        }
    }

    public function __destruct()
    {
        if (!empty($this->fds)) {
            foreach ($this->fds as $fd) {
                fclose($fd);
            }
        }
    }

    /**
     * 添加文件監控
     */
    public function addFile($file)
    {
        $file = realpath($file);
        $fd = inotify_init();
        $fid = (int)$fd;
        //保存inotify資源
        $this->fds[$fid] = $fd;
        //設置爲非阻塞模式
        stream_set_blocking($this->fds[$fid], 0);
        //保存文件路徑
        $this->paths[$fid] = $file;
        //保存監控描述符
        $this->wds[$fid] = inotify_add_watch($this->fds[$fid], $file, self::MONITOR_EVENT);
    }

    /**
     * 添加目錄監控
     */
    public function addDir($dir)
    {
        $dir = realpath($dir);
        if ($dh = opendir($dir)) {
            //將目錄加入監控中
            $fd = inotify_init();
            //通常文件的資源描述符是一個整形,能夠用來當索引
            $fid = (int)$fd;
            $this->fds[$fid] = $fd;
            stream_set_blocking($this->fds[$fid], 0);
            $this->paths[$fid] = $dir;
            $this->wds[$fid] = inotify_add_watch($this->fds[$fid], $dir, self::MONITOR_EVENT);
            //遍歷目錄下文件
            while (($file = readdir($dh)) !== false) {
                if ($file == '.' || $file == '..') {
                    continue;
                }
                $file = $dir . DIRECTORY_SEPARATOR . $file;
                if (is_dir($file)) {
                    $this->addDir($file);
                }
            }
            closedir($dh);
        }
    }

    /**
     * 移除監控
     */
    public function remove($fid)
    {
        unset($this->paths[$fid]);
        fclose($this->fds[$fid]);
        unset($this->fds[$fid]);
    }

    /**
     * 運行監控
     */
    public function run()
    {
        while (true) {
            $reads = $this->fds;
            $write = [];
            $except = [];
            if (stream_select($reads, $write, $except, $this->timeout) > 0) {
                if (!empty($reads)) {
                    foreach ($reads as $read) {
                        //從可讀流中讀取數據
                        $events = inotify_read($read);
                        //資源描述符,整形
                        $fid = (int)$read;
                        //獲取inotify實例的路徑
                        $path = $this->paths[$fid];
                        foreach ($events as $event) {
                            $file = $path . DIRECTORY_SEPARATOR . $event['name'];
                            switch ($event['mask']) {
                                case IN_CREATE:
                                case 1073742080:
                                    if (is_dir($file)) {
                                        echo 'add ...', PHP_EOL;
                                        echo 'fid : ', $fid, PHP_EOL;
                                        $this->addDir($file);
                                    }
                                    break;
                                case IN_DELETE_SELF:
                                    if (is_dir($file)) {
                                        echo 'remove ...', PHP_EOL;
                                        echo 'fid : ', $fid, PHP_EOL;
                                        $this->remove($fid);

                                        $key = array_search($read, $reads);
                                        unset($reads[$key]);
                                    }
                                    break;
                            }
                            echo $event['name'], ' --- ', self::EVENT_MASK[$event['mask']], PHP_EOL;
                        }
                    }
                }
            } else {
                echo '------------------', PHP_EOL;
                echo '當前監控的路徑列表', PHP_EOL;
                print_r($this->paths);
                echo '------------------', PHP_EOL;
            }
        }
    }
}

(new InotifyMonitor(['./tmp', './test.php']))->run();
相關文章
相關標籤/搜索