如果站長沒有自己的伺服器,而是租用虛擬主機,就無法進入伺服器系統進行上述操作。這個時候應該如何進行php定時任務呢?其實方法又有多個。
使用ignore_user_abort(true)和sleep死循環。
在一個php文檔的開頭直接來一句:
ignore_user_abort(true);
這時,通過url訪問這個php的時候,即使用戶把瀏覽器關掉(斷開連接),php也會在伺服器上繼續執行。利用這個特性,我們可以實現非常牛的功能,也就是通過它來實現定時任務的激活,激活之後就隨便它自己怎麼辦了,實際上就有點類似於後台任務?
而sleep(n)則是指當程序執行到這裡時,暫時不往下執行,而是休息n秒鐘。如果你訪問這個php,就會發現頁面起碼要加載n秒鐘。實際上,這種長時間等待的行為是比較消耗資源的,不能大量使用。
那麼定時任務到底怎麼實現呢?使用下面的代碼即可實現:
複製代碼。
<?php
ignore_user_abort(true);
set_time_limit(0);
date_default_timezone_set('prc');// 切換到中國的時間。
$run_time = strtotime('+1 day');// 定時任務第一次執行的時間是明天的這個時候。
$interval = 3600*12;// 每12個小時執行一次。
if(!file_exists(dirname(__file__).'/cron-run')) exit();// 在目錄下存放一個cron-run文件,如果這個文件不存在,說明已經在執行過程中了,該任務就不能再激活,執行第二次,否則這個文件被多次訪問的話,伺服器就要崩潰掉了。
do {
if(!file_exists(dirname(__file__).'/cron-switch')) break;// 如果不存在cron-switch這個文件,就停止執行,這是一個開關的作用。
$gmt_time = microtime(true);// 當前的運行時間,精確到0.0001秒。
$loop = isset($loop) &&$loop?$loop:$run_time - $gmt_time;// 這裡處理是為了確定還要等多久才開始第一次執行任務,$loop就是要等多久才執行的時間間隔。
$loop = $loop >0?$loop:0;
if(!$loop) break;// 如果循環的間隔為零,則停止。
sleep($loop);
//。
// 執行某些代碼。
//。
@unlink(dirname(__file__).'/cron-run');// 這裡就是通過刪除cron-run來告訴程序,這個定時任務已經在執行過程中,不能再執行一個新的同樣的任務。
$loop = $interval;
} while(true);
複製代碼。
通過執行上面這段php代碼,即可實現定時任務,直到你刪除cron-switch文件,這個任務才會停止。
但是有一個問題,也就是如果用戶直接訪問這個php,實際上沒有任何作用,頁面也會停在這個地方,一直處於加載狀態,有沒有一種辦法可以消除這種影響呢?fsockopen幫我們解決了這個問題。
fsockopen可以實現在請求訪問某個文件時,不必獲得返回結果就繼續往下執行程序,這是和curl通常用法不一樣的地方,我們在使用curl訪問網頁時,一定要等curl加載完網頁後,才會執行curl後面的代碼,雖然實際上curl也可以實現「非阻塞式」的請求,但是比fsockopen複雜的多,所以我們優先選擇fsockopen,fsockopen可以在規定的時間內,比如1秒鐘以內,完成對訪問路徑發出請求,完成之後就不管這個路徑是否返回內容了,它的任務就到這裡結束,可以繼續往下執行程序了。利用這個特性,我們在正常的程序流中加入fsockopen,對上面我們創建的這個定時任務php的地址發出請求,即可讓定時任務在後台執行。如果上面這個php的url地址是www.yourdomain.com/script.php,那麼我們在編程中,可以這樣:
複製代碼。
//。
// 正常的php執行程序。
//。
// 遠程請求(不獲取內容)函數,下面可以反覆使用。
function _sock($url) {
$host = parse_url($url,php_url_host);
$port = parse_url($url,php_url_port);
$port = $port?$port:80;
$scheme = parse_url($url,php_url_scheme);
$path = parse_url($url,php_url_path);
$query = parse_url($url,php_url_query);
if($query) $path.= '?'.$query;
if($scheme == 'https') {
$host = 'ssl://'.$host;
}
$fp = fsockopen($host,$port,$error_code,$error_msg,1);
if(!$fp) {
return array('error_code' =>$error_code,'error_msg' =>$error_msg);
}
else {
stream_set_blocking($fp,true);//開啟了手冊上說的非阻塞模式。
stream_set_timeout($fp,1);//設置超時。
$header = "get $path http/1.1\r\n";
$header.="host:$host\r\n";
$header.="connection:close\r\n\r\n";//長連接關閉。
fwrite($fp,$header);
usleep(1000);// 這一句也是關鍵,如果沒有這延時,可能在nginx伺服器上就無法執行成功。
fclose($fp);
return array('error_code' =>0);
}
}
_sock('www.yourdomain.com/script.php');
//。
// 繼續執行其他動作。
//。
複製代碼。
把這段代碼加入到某個定時任務提交結果程序中,在設置好時間後,提交,然後執行上面這個代碼,就可以激活該定時任務,而且對於提交的這個用戶而言,沒有任何頁面上的堵塞感。
借用用戶的訪問行為來執行某些延遲任務。
但是上面使用sleep來實現定時任務,是效率很低的一種方案。我們希望不要使用這種方式來執行,這樣的話就可以解決效率問題。我們借用用戶訪問行為來執行任務。用戶對網站的訪問其實是一個非常豐富的行為資源,包括搜尋引擎蜘蛛對網站的訪問,都可以算作這個類型。在用戶訪問網站時,內部加一個動作,去檢查任務列表中是否存在沒有被執行的任務,如果存在,就將這個任務執行。對於用戶而言,利用上面所說的fsockopen,根本感覺不到自己的訪問竟然還做出了這樣的貢獻。但是這種訪問的缺點就是訪問很不規律,比如你希望在凌晨2點執行某項任務,但是這個時間段非常倒霉,沒有用戶或任何行為到達你的網站,直到早上6點才有一個新訪問。這就導致你原本打算2點執行的任務,到6點才被執行。
這裡涉及到一個定時任務列表,也就是說你需要有一個列表來記錄所有任務的時間、執行什麼內容。一般來說,很多系統會採用資料庫來記錄這些任務列表,比如wordpress就是這樣做的。我則利用文件讀寫特性,提供了託管在github上的開源項目php-cron,你可以去看看。總之,如果你想要管理多個定時任務,靠上面的單個php是無法合理布局的,必須想辦法構建一個schedules列表。由於這裡面的邏輯比較複雜,就不再詳細闡述,我們僅停留在思路層面上?
借用第三方定時任務跳板。
很好玩的是,一些服務商提供了各種類型的定時任務,例如阿里雲的ace提供了單獨的定時任務,你可以填寫自己應用下的某個uri。百度雲bce提供了伺服器監測功能,每天會按照一定的時間規律訪問應用下的固定uri。類似的第三方平台上還有很多定時任務可以用。你完全可以用這些第三方定時任務作為跳板,為你的網站定時任務服務。比如說,你可以在阿里雲ace上建立一個每天凌晨2點的定時任務,執行的uri是/cron.php。然後你創建一個cron.php,裡面則採用fsockopen去訪問你真正要執行某些任務的網站的url,例如上面的www.yourdomain.com/script.php,而且在cron.php中還可以訪問多個url。然後把cron.php上傳到你的ace上面去,讓ace的定時任務去訪問/cron.php,然後讓cron.php去遠程請求目標網站的定時任務腳本。
循環利用include包含文件(待驗證)。
php面向過程的特性使得其程序是從上往下執行的,利用這個特性,在我們使用include某個文件時,就會執行被引入的文件,知道include的文件內程序執行完之後,再往下執行。如果我們創建一個循環,再利用sleep,不斷的include某個文件,使循環執行某段程序,則可以達到定時執行的目的。我們再進一步,並不是利用while(true)來實現循環,而是利用被include文件本身再include自身來實現循環,比如我們創建一個do.php,它的內容如下:
複製代碼。
if(...) exit();// 通過某個開關來關閉執行。
//。
// 執行某些程序。
//。
sleep($loop);// 這個$loop在include('do.php');之前賦值。
include(dirname(__file__).'/do.php');
複製代碼。
其實通過這種方法執行和while的思路也像。而且同樣用到sleep,效率低。
php定時任務是一個非常有意思的東西,雖然說實話,用系統的php.exe去直接執行php文件的效率更高,但是對於很多普通站長而言,虛擬主機是無法做到直接php執行原生程序的。本文僅提供一些解決的思路,我也僅僅是在學習中,有很多問題或表述都不正確,希望你指出來;你可以通過本文的思路,開發出自己的一種解決方案,希望你能將方案發布,並與我一起探討。
- 編程問答
- 答案列表
非自有伺服器 虛擬主機 上實現php定時任務[朗讀]
加入收藏