├── .gitattributes ├── README.md ├── cron ├── Cron.DB.Class.php ├── Cron.FILE.Class.php ├── cron.log └── cron.php ├── demo ├── demo.php ├── test.log └── test.php └── schedules └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-cron 2 | 实现PHP Cron,也就是PHP定时任务,通过本地文件记录schedules,然后通过fsockopen实现非阻塞式的后台访问对应的url来实现定时任务,通过sleep实现定时,如果错过任务,则通过用户访问来执行该任务 3 | 4 | ## 文件目录 5 | * cron Cron类,命名空间PHPCron,里面实现了任务操作,如添加、更新、删除、激活、运行等 6 | - Cron.Class.php 类 7 | - cron.php 用来激活和调用的入口文件 8 | - cron.log 用来记录动作的log文件 9 | * schedules 任务列表,添加的任务会以"任务名.php"的格式保存在这个文件夹内,任务信息将会序列化保存,反序列化后就是一个数组 10 | * demo 用来演示使用Cron的效果文件 -------------------------------------------------------------------------------- /cron/Cron.DB.Class.php: -------------------------------------------------------------------------------- 1 | db = mysql_connect($host,$user,$pass); 14 | mysql_select_db($name,$this->db); 15 | // 用来执行某个任务的cron.php文件url,一般要带上?cron=name才可以,它是和Cron:_sock()配合用的,如果不是放在根目录下,需要自己设定具体的访问路径 16 | if(defined('CRON_URL')) { 17 | $this->cron_url = CRON_URL; 18 | } 19 | else { 20 | $this->cron_url = 'http://'.$_SERVER['SERVER_NAME'].'/php-cron/cron/cron.php'; 21 | } 22 | } 23 | 24 | // 判断任务是否存在 25 | function exists($name) { 26 | } 27 | 28 | // 获取某个任务的信息 29 | function get($name = null) { 30 | } 31 | 32 | // 添加一个任务(添加到列表中,而不是马上执行) 33 | function add($name,$microtime,$interval,$url) { 34 | } 35 | 36 | // 更新任务,当任务不存在时,添加这个任务 37 | function update($name,$microtime,$interval,$url) { 38 | } 39 | 40 | // 删除一个任务 41 | function delete($name) { 42 | } 43 | 44 | // 立即执行一个任务,执行时,就会更新任务信息 45 | function run($name) { 46 | } 47 | 48 | // 定时执行一个任务,通过sleep实现开始定时执行 49 | function set($name) { 50 | } 51 | 52 | // 检查定时任务,如果某个任务没有执行,就在检查的时候执行 53 | function check() { 54 | } 55 | 56 | /* 57 | * 公有函数,并非cron内含动作 58 | */ 59 | // 远程请求(不获取内容)函数 60 | function _sock($url) { 61 | $host = parse_url($url,PHP_URL_HOST); 62 | $port = parse_url($url,PHP_URL_PORT); 63 | $port = $port ? $port : 80; 64 | $scheme = parse_url($url,PHP_URL_SCHEME); 65 | $path = parse_url($url,PHP_URL_PATH); 66 | $query = parse_url($url,PHP_URL_QUERY); 67 | if($query) $path .= '?'.$query; 68 | if($scheme == 'https') { 69 | $host = 'ssl://'.$host; 70 | } 71 | if($fp = @fsockopen($host,$port,$error_code,$error_msg,5)) { 72 | stream_set_blocking($fp,0);//开启了手册上说的非阻塞模式 73 | $header = "GET $path HTTP/1.1\r\n"; 74 | $header.="Host: $host\r\n"; 75 | $header.="Connection: Close\r\n\r\n";//长连接关闭 76 | fwrite($fp, $header); 77 | fclose($fp); 78 | } 79 | return array($error_code,$error_msg); 80 | } 81 | // 记录log 82 | function _log($msg) { 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /cron/Cron.FILE.Class.php: -------------------------------------------------------------------------------- 1 | schedules_dir = realpath(dirname(__FILE__).'/../schedules'); 19 | // 用来执行某个任务的cron.php文件url,一般要带上?cron=name才可以,它是和Cron:_sock()配合用的,如果不是放在根目录下,需要自己设定具体的访问路径 20 | if(defined('CRON_URL')) { 21 | $this->cron_url = CRON_URL; 22 | } 23 | else { 24 | $this->cron_url = 'http://'.$_SERVER['SERVER_NAME'].'/php-cron/cron/cron.php'; 25 | } 26 | } 27 | 28 | // 判断任务是否存在 29 | function exists($name) { 30 | $file = $this->schedules_dir.'/'.$name.'.php'; 31 | if(!file_exists($file)) return false; 32 | return true; 33 | } 34 | 35 | // 获取某个任务的信息 36 | function get($name = null) { 37 | // 为空是,获取任务列表 38 | if(empty($name)) { 39 | $schedules_files = $this->_scandir($this->schedules_dir); 40 | $schedules = array(); 41 | if(!empty($schedules_files)) foreach($schedules_files as $file) { 42 | if($file == '.' || $file == '..') { continue; } 43 | $schedules[] = basename($file,'.php'); 44 | } 45 | return $schedules; 46 | } 47 | // 否则获取对应的任务信息 48 | $file = $this->schedules_dir.'/'.$name.'.php'; 49 | if(!file_exists($file)) return false; // 不存在该任务时,返回错误 50 | $file_content = file($file); 51 | $schedule = unserialize($file_content[1]); 52 | return $schedule; 53 | } 54 | 55 | // 添加一个任务(添加到列表中,而不是马上执行) 56 | function add($name,$microtime,$interval,$url) { 57 | if($this->exists($name)) return false; // 如果已经存在该任务了,就只能使用update,而不能使用add 58 | return $this->update($name,$microtime,$interval,$url); 59 | } 60 | 61 | // 更新任务,当任务不存在时,添加这个任务 62 | function update($name,$microtime,$interval,$url) { 63 | $file = $this->schedules_dir.'/'.$name.'.php'; 64 | $gmt_time = microtime(true); 65 | if(file_exists($file)) { 66 | if(filemtime($file) > $gmt_time - 1) return 0; // 如果文件被极速写入,为了防止文件被同时更改,则返回错误 67 | $schedule = $this->get($name); 68 | } 69 | else { 70 | $schedule = array('name' => $name); 71 | } 72 | $schedule['next_run_time'] = $microtime; 73 | $schedule['interval'] = $interval; 74 | $schedule['url'] = $url; 75 | $schedule = serialize($schedule); 76 | // 创建任务信息文件 77 | if($handle = @fopen($file, "w")) { 78 | fwrite($handle,''."\r\n"); 79 | fwrite($handle,$schedule); 80 | fclose($handle); 81 | } 82 | else { 83 | return null; // 用null表示写入失败 84 | } 85 | return true; 86 | } 87 | 88 | // 删除一个任务 89 | function delete($name) { 90 | $file = $this->schedules_dir.'/'.$name.'.php'; 91 | @unlink($file); 92 | } 93 | 94 | // 立即执行一个任务,执行时,就会更新任务信息 95 | function run($name) { 96 | $file = $this->schedules_dir.'/'.$name.'.php'; 97 | if(!file_exists($file)) return false; // 不存在该任务时,返回错误 98 | $gmt_time = microtime(true); 99 | if(filemtime($file) > $gmt_time - 1) return 0; // 如果文件被极速写入,为了防止文件被同时更改,则返回错误 100 | $schedule = $this->get($name); 101 | // 执行url 102 | $url = $schedule['url']; 103 | list($error_code,$error_msg) = $this->_sock($url); 104 | // 记录这次执行的情况 105 | $msg = date('Y-m-d H:i:s')." 执行任务:$name 结果:$error_code $error_msg\n"; 106 | $this->_log($msg); 107 | // 更新schedule 108 | $schedule['last_run_time'] = $schedule['next_run_time']; 109 | $schedule['next_run_time'] += $schedule['interval']; 110 | $schedule['status'] = 1; 111 | $schedule = serialize($schedule); 112 | if($handle = @fopen($file, "w")) { 113 | fwrite($handle,''."\r\n"); 114 | fwrite($handle,$schedule); 115 | fclose($handle); 116 | } 117 | else { 118 | return null; // 用null表示写入失败 119 | } 120 | return true; 121 | } 122 | 123 | // 定时执行一个任务,通过sleep实现开始定时执行 124 | function set($name) { 125 | // 执行中的任务就不能再激活了 126 | $schedule = $Cron->get($name); 127 | if(isset($schedule['status']) && $schedule['status'] == 1) { 128 | echo 'schedule is running...'; 129 | exit(); 130 | } 131 | // 通过远程访问cron.php 132 | $url = $this->cron_url.'?cron='.$name; 133 | list($error_code,$error_msg) = $this->_sock($url); 134 | $msg = date('Y-m-d H:i:s')." 激活任务:$name 结果:$error_code $error_msg\n"; 135 | $this->_log($msg); 136 | } 137 | 138 | // 检查定时任务,如果某个任务没有执行,就在检查的时候执行 139 | function check() { 140 | $url = $this->cron_url; 141 | list($error_code,$error_msg) = $this->_sock($url); 142 | $msg = date('Y-m-d H:i:s')." 检查任务:$name 结果:$error_code $error_msg\n"; 143 | $this->_log($msg); 144 | } 145 | 146 | /* 147 | * 公有函数,并非cron内含动作 148 | */ 149 | // 远程请求(不获取内容)函数 150 | function _sock($url) { 151 | $host = parse_url($url,PHP_URL_HOST); 152 | $port = parse_url($url,PHP_URL_PORT); 153 | $port = $port ? $port : 80; 154 | $scheme = parse_url($url,PHP_URL_SCHEME); 155 | $path = parse_url($url,PHP_URL_PATH); 156 | $query = parse_url($url,PHP_URL_QUERY); 157 | if($query) $path .= '?'.$query; 158 | if($scheme == 'https') { 159 | $host = 'ssl://'.$host; 160 | } 161 | if($fp = @fsockopen($host,$port,$error_code,$error_msg,5)) { 162 | stream_set_blocking($fp,0);//开启了手册上说的非阻塞模式 163 | $header = "GET $path HTTP/1.1\r\n"; 164 | $header.="Host: $host\r\n"; 165 | $header.="Connection: Close\r\n\r\n";//长连接关闭 166 | fwrite($fp, $header); 167 | fclose($fp); 168 | } 169 | return array($error_code,$error_msg); 170 | } 171 | // 记录log 172 | function _log($msg) { 173 | $file = dirname(__FILE__).'/cron.log'; 174 | $fp = fopen($file,'a'); 175 | fwrite($fp,$msg); 176 | fclose($fp); 177 | } 178 | 179 | /* 180 | * 私有函数 181 | */ 182 | // 浏览目录 183 | private function _scandir($dir) { 184 | if(function_exists('scandir')) { 185 | return scandir($dir); 186 | } 187 | else { 188 | $handle = @opendir($dir); 189 | $arr = array(); 190 | while(($arr[] = @readdir($handle)) !== false) {} 191 | @closedir($handle); 192 | $arr = array_filter($arr); 193 | return $arr; 194 | } 195 | } 196 | } -------------------------------------------------------------------------------- /cron/cron.log: -------------------------------------------------------------------------------- 1 | 2015-07-02 12:05:19 激活任务:mycron 结果:0 2 | 2015-07-02 12:07:29 激活任务:mycron 结果:0 3 | 2015-07-02 12:08:10 激活任务:mycron 结果:0 4 | 2015-07-02 12:09:45 激活任务:mycron 结果:0 5 | 2015-07-02 12:12:46 激活任务:mycron 结果:0 6 | 2015-07-02 12:13:11 激活任务:mycron 结果:0 7 | 2015-07-02 12:14:30 激活任务:mycron 结果:0 8 | 2015-07-02 12:15:14 激活任务:mycron 结果:0 9 | 2015-07-02 12:15:46 激活任务:mycron 结果:0 10 | 2015-07-02 12:15:56 激活任务:mycron 结果:0 11 | 2015-07-02 12:18:05 激活任务:mycron 结果:0 12 | 2015-07-02 12:18:18 激活任务:mycron 结果:0 13 | 2015-07-02 12:19:04 激活任务:mycron 结果:0 14 | 2015-07-02 12:19:25 激活任务:mycron 结果:0 15 | 2015-07-02 12:19:49 激活任务:mycron 结果:0 16 | 2015-07-02 12:20:14 激活任务:mycron 结果:0 17 | 2015-07-02 12:20:25 激活任务:mycron 结果:0 18 | 2015-07-02 20:21:22 激活任务:mycron 结果:0 19 | 2015-07-02 20:21:45 激活任务:mycron 结果:0 20 | 2015-07-02 20:22:05 激活任务:mycron 结果:0 21 | 2015-07-02 20:22:13 执行任务:mycron 结果:0 22 | 2015-07-02 20:22:33 执行任务:mycron 结果:0 23 | 2015-07-02 20:23:13 执行任务:mycron 结果:0 24 | 2015-07-02 20:23:25 激活任务:mycron 结果:0 25 | 2015-07-02 20:23:33 执行任务:mycron 结果:0 26 | 2015-07-02 20:24:14 执行任务:mycron 结果:0 27 | -------------------------------------------------------------------------------- /cron/cron.php: -------------------------------------------------------------------------------- 1 | get(); 15 | if(!empty($schedules)) foreach($schedules as $shd) { 16 | // 如果当前时间比该任务的下一次执行时间还要大,说明定时任务失败,通过该被动执行来完成定时任务 17 | if($gmt_time > $shd['next_run_time']) { 18 | $Cron->run($shd['name']); 19 | break; 20 | } 21 | } 22 | exit(); 23 | } 24 | 25 | // 如果存在cron参数,就以它作为任务名称,去激活该任务 26 | $name = $_GET['cron']; 27 | $loop = 0; 28 | // 如果多次访问这个任务,这个任务是否会被激活多次呢 29 | $schedule = $Cron->get($name); 30 | if(isset($schedule['status']) && $schedule['status'] == 1) { 31 | echo 'schedule is running...'; 32 | exit(); 33 | } 34 | 35 | do { 36 | $schedule = $Cron->get($name); 37 | if(!$schedule) break;// 如果在任务执行过程中,发现这个任务已经被删除了,则停止执行 38 | $gmt_time = microtime(true); 39 | $loop = $loop ? $loop : $schedule['next_run_time'] - $gmt_time; 40 | $loop = $loop > 0 ? $loop : 0; 41 | if(!$loop) break; // 如果循环的间隔为零,则停止 42 | sleep($loop); 43 | $Cron->run($name); 44 | $loop = $schedule['interval']; 45 | } while(true); -------------------------------------------------------------------------------- /demo/demo.php: -------------------------------------------------------------------------------- 1 | update($name,microtime(true) + 30,60,'http://'.$_SERVER['SERVER_NAME'].'/php-cron/demo/test.php'); 19 | 20 | /* 21 | * demo目录下的test.php是一个用来测试定时任务是否执行了的文件,每一次定时任务执行时,都会在test.log文件末尾添加执行任务的信息,你可以通过查看test.log来看执行的情况 22 | * 要关闭该定时任务,只需要删除../schedules目录中对应的文件即可,没有文件,下一次定时任务时就会暂停 23 | */ 24 | 25 | // 然后立即定时任务 26 | //$Cron->run($name); 27 | 28 | // 等待1秒,让文件写入后再执行 29 | sleep(1); 30 | 31 | // 激活定时任务 32 | $Cron->set($name); 33 | 34 | // 删除这个任务,需将上面两个动作注释掉 35 | //$Cron->delete($name); -------------------------------------------------------------------------------- /demo/test.log: -------------------------------------------------------------------------------- 1 | 执行时间:2015-07-02 12:22:13 2 | 执行时间:2015-07-02 12:22:33 3 | 执行时间:2015-07-02 12:23:13 4 | 执行时间:2015-07-02 12:23:33 5 | 执行时间:2015-07-02 12:24:14 6 | -------------------------------------------------------------------------------- /demo/test.php: -------------------------------------------------------------------------------- 1 |