一、安裝inotify擴展
1、下載inotify擴展源碼
https://pecl.php.net/package/inotify
對於php7以上版本,請下載 inotify-2.0.0.tgz。
2、編譯安裝
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
3、修改php.ini,添加
extension = inotify.so
二、使用inotify進行文件監控
php的inotify擴展提供了監控文件或目錄的功能,可以用來實現,服務的熱更新,或安全監控。
inotify一共提供了5個函數:
1、inotify_init() 用於初始化一個實例,返回的是一個 stream 資源對象,可以被標准stream函數使用,如stream_set_blocking()。
2、inotify_add_watch() 將一個文件或目錄添加到監控列表中,如果存在,則替換。
3、inotify_read() 從 inotify 實例中讀取事件,如果沒有返回false,如果有,返回一個 inotify 事件數組。
4、inotify_queue_len() 返回隊列中事件的個數,可以理解為系統把 inotify_add_watch 關注的事件一個個加入隊列,通過 inotify_read() 讀取事件。
5、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);
三、我們通過非阻塞的方式來監控文件
<?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);
四、封裝一個類,用來監控文件或目錄。
<?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();
