標籤:http io ar os 使用 sp for 檔案 資料
小結檔案的鎖定機制、上傳和下載
1.檔案鎖定 現在都在講究什麼分布式、並發等,實際上檔案的操作也是並發的,在網路環境下,多個使用者在同一時刻訪問頁面,對同一伺服器上的同一檔案進行著讀取,如果,這個使用者剛好讀到一半,另一個使用者就寫入了訊息,那麼前一個使用者讀到的就是錯誤資料,在資料庫裡面好像是稱為髒資料,而如果某使用者寫到一半時,另一使用者也對該檔案進行寫操作,那麼就造成了寫入資料的混亂和錯誤,因此才php有一個鎖機制,類似於資料庫的鎖,當某使用者在對檔案操作時就加上某種鎖,使得在同一時間其他使用者不能對該檔案進行操作或只能進行有限的操作,來保證在這些情況下的檔案資料的正確性。 主要使用flock函數,原型:bool flock(resource $handle , int $operation [, int &$wouldblock ]),第一個參數是指向檔案的控制代碼變數,第二個是加鎖的方式,分別為 LOCK_SH:共用鎖定(share),在讀取檔案時加的鎖,加鎖後就其他使用者不能再對該檔案進行寫,但可以讀取該檔案內容; LOCK_EX:獨佔鎖定(exclude),或者叫獨佔鎖,在寫檔案時使用,加了該鎖後,只能是目前使用者進行寫操作,其他的使用者不能讀取和寫入; LOCK_NB:附加鎖,在檔案鎖定短時間大量使用者的訪問操作可能會造成flock在鎖定時堵塞,如果再加上該鎖後可避免該情況(是不是這麼一弄就能解決大量讀寫操作的問題,怕不行...); LOCK_UN:釋放鎖,對前面的各種鎖進行一次性釋放,解鎖。 如果容易堵塞,還可使用第三個參數wouldblock,如果把它設定為1,在鎖定後就會阻擋其他進程來進行一些操作,但是windows上不支援,另外附加鎖LOCK_NB,windows也是不支援的。 另外,關閉控制代碼變數的fclose操作也可以釋放這些鎖。 廢話少說,看代碼 複製代碼<?php function readFileData($filename){ if(true == ($handle = fopen($filename, ‘r‘))){ if(flock($handle, LOCK_SH+LOCK_NB)){ // 加共用鎖定和附加鎖,附加鎖防止阻塞 $str = ‘‘; while(!feof($handle)){ $str .= fread($handle, 128); } flock($handle, LOCK_UN); // 釋放該鎖 fclose($handle); return $str; } else{ echo ‘add a share lock failed‘; return ‘‘; } } else{ return ‘‘; } }複製代碼 注意使用多重鎖的方式,是LOCK_SH+LOCK_NB,,在獨佔鎖定時也可這麼用,它們都是枚舉變數。對於寫操作類似 複製代碼<?php function writeInFile($filename, $data){ if(true == ($handle = fopen($filename, ‘a‘))){ if(flock($handle, LOCK_EX)){ //加上獨佔鎖定 fwrite($handle, $data); flock($handle, LOCK_UN); //釋放鎖 fclose($handle); } else{ echo ‘add exclusive lock failed‘; return; } } }複製代碼 實際上這也是解決php來實現多進程/線程讀取檔案的方式,php沒有多線程這麼一說,但加鎖機制可以來類比這種方式,實現對檔案某些操作的隊列處理,面試時別說你不知道-_- 2.檔案上傳 檔案上傳就是將本地的檔案上傳到伺服器上,我們在用度場的雲、鵝場的雲上傳檔案時均是如此,當然實際情況肯定比這簡單程式複雜。HTTP協議實現了檔案的上傳機制,首先要在本地選擇上傳的檔案,上傳到伺服器後,服務端又要做一些處理,為此用戶端和服務端均要做一些設定。 1、用戶端 檔案上傳最基本的方法是通過form表單進行POST傳遞檔案,實際上通過PUT方法也可以上傳檔案,只不過這種方法不安全,需要配置一些安全驗證機制,這裡唯寫最常用的方式。form表單的input標籤可以設定成檔案上傳按鈕type="file",直接解決了如何選擇檔案的問題,接下來需要設定form的兩個屬性:enctype和method enctype:設定成multipart/form-data method:設定成post 關於enctype屬性設定可參考W3School的解釋 第一條是預設的值,在我們使用HTTP協議傳遞一般的表單資料時,實際上預設對資料進行了分塊編碼,比如預設的urlencode方式(空格轉為+,其他非字母字元轉為%加兩個十六進位大寫數);當enctype設定為第二個時,不會進行字元編碼,使用上傳控制項(input標籤type設為file時即是)上傳檔案,必須設定為這個值;第三個則是值對空格編碼為+,但不對非字母字元進行編碼。 我們知道GET方法一般用於擷取資料,且傳遞資料大小有限,而且POST方法可以傳遞比GET大得多的資料。 form的屬性設定完成後,還要傳遞一個值過去,使用隱藏欄位(<input type="hidden">),它的name屬性設為MAX_FILE_SIZE,之所以要設定這個值,是先大概定一個檔案尺寸值,避免在使用者傳一個大檔案傳了半天再告訴他:sorry,你的檔案太大了-_-它的value屬性值就是檔案的size,以位元組為單位。當然某些書上說,這個值只是作為參考,可輕易進行欺騙,這裡只是象徵性的表示,很可惜我這隻菜鳥對安全瞭解甚少,只知道普通注入、XSS等,暫且用著吧。 那麼就可以寫一個簡單得不能再簡單的頁面了,作為用戶端用: <form method="post" action="upload.php" enctype="multipart/form-data"> <input type="hidden" name="MAX_FILE_SIZE" value="1000000" /> 選擇檔案: <input type="file" name="uploadFile" value="upload"/><br/> <input type="submit" name="submit" value="上傳" /></form> 2、服務端 檔案上傳到了伺服器上還要經過一些處理過程,就像網購派送快遞,到了目的地也還得分個類,確認下目的地對錯吧。到了目的地的後續處理需要php指令碼,上面在提交表單時的action屬性就指定了提交的處理指令碼。我們知道在php中,$_POST儲存的是post傳遞的資料,而上傳檔案的相關資訊儲存在$_FILES裡邊,假設服務端指令碼是這樣的: <?php echo ‘_FILES: <pre>‘; print_r($_FILES); echo ‘_POST: <pre>‘; print_r($_POST); 不管服務端如何處理的,先看看這兩個數組裡面有什麼: 看FILES數組的選項就猜得到,這些就是上傳檔案的名字、類型、尺寸、錯誤資訊等等,還有這個FILES是二維數組。在弄清楚這些選項之前有必要瞭解幾個php配置選項,開啟php.ini檔案,找到下面四項(其實看注釋也明白了): file_uploads:是否允許通過HTTP傳遞檔案,預設是On允許; upload_max_filesize:允許傳遞檔案的最大大小,以M為單位,這是服務端設定檔設定的選項; max_file_uploads:一次請求所允許傳遞的做多檔案個數; post_max_size:通過POST傳遞資料的最大大小,因為檔案傳遞也是post方式,也算post傳遞,需要特別注意的是,它必須要大於upload_max_filesize選項,因為在一次post傳遞過程中不僅會上傳檔案,還會傳遞其他數值,比如上面的POST數組中的資料,必須考慮到,比如upload_max_filesize設為150M,這個就可以設為200M; upload_tmp_dir:上傳檔案的臨時目錄,設定檔裡面預設為空白,會使用作業系統預設的臨時目錄,因此上面的FILES數組中的tmp_name中的眼熟的路徑就可以解釋了,使用windows預設的存放臨時檔案的目錄,而且伺服器預設對檔案名稱作了修改。 那麼FILES數組中的uploadFile哪裡來的,為什麼要用它做鍵名,這是因為在上傳控制項的name屬性就是uploadFile,它標記的是這個控制項的上傳檔案資訊,因此我們可以放多個上傳控制項,設定不同的name,當然設定一樣的name也可以,完全可以把它們全放在一個數組裡邊,如<input type="file" name="upload[]">。 現在回過頭看FILE數組的鍵名代表的資訊,type是MIME類型,以/分隔,前面是主要類型,後面是具體檔案類型,error肯定表示錯誤,有這麼幾種情況,0:沒有錯誤,上傳成功; 1:檔案超過了PHP配置指令中的upload_max_filesize規定的大小; 2:檔案超過HTML表單中MAX_FILE_SIZE規定的大小,3:檔案只有部分上傳; 4:沒有檔案上傳。現在關於FILES數組的問題全部明白了。 問題是,是不是上傳成功就不做任何處理了,當然不是,總不能全堆在一個臨時目錄裡面,上傳多了必然就要將檔案移到別的地方,而php提供了專門而安全的函數。is_uploaded_file函數,判斷是否通過HTTP POST上傳,可以確保惡意的使用者去欺騙指令碼而管理這些檔案,例如/etc/pass(又是linux...),至於具體怎樣,我還不清楚。move_uploaded_file函數,將上傳檔案移動到新位置,同時還可判斷檔案是否為合法上傳,即通過HTTP POST方式,他們運行成功均返回布爾類型true。 扯了半天,上傳檔案大概要經過這樣幾個步驟: 1、用戶端寫好上傳控制項指令碼,並傳遞一個限制檔案大小的隱藏值; 2、服務端首先判斷FILES數組error值,看是否出錯; 3、判斷是否為允許上傳的類型(可以不判斷); 4、判斷在服務端指令碼裡邊是否超過指定的檔案大小; 5、上傳到臨時位置,產生新檔案名稱(防止把已有同名檔案覆蓋掉),檢查並移動到新目錄下。 用戶端準備工作剛已做,看服務端處理代碼: 複製代碼<?php $typeWhiteList = array(‘txt‘, ‘doc‘, ‘php‘, ‘zip‘, ‘exe‘); // 類型白名單,過濾不允許上傳的檔案類型 $max_size = 1000000; // 大小限制 為1M $upload_path = ‘D:/WAMP/upload/‘; // 指定移至的目錄 // 1、判斷是否成功上傳到伺服器 $error = $_FILES[‘uploadFile‘][‘error‘]; if($error > 0){ switch($error){ case 1: exit(‘超過php配置的最大檔案上傳限制‘); case 2: exit(‘超過HTML表單的最大檔案上傳限制‘); case 3: exit(‘檔案只有部分被上傳‘); case 4: exit(‘沒有上傳任何檔案‘); default: exit(‘未知類型錯誤‘); } } // 2、判斷是否為允許上傳的類型 $extension = pathinfo($_FILES[‘uploadFile‘][‘name‘], PATHINFO_EXTENSION); // 擷取副檔名 if(!in_array($extension, $typeWhiteList)){ if($extension == ‘‘) exit(‘不允許上傳空類型檔案‘); else exit(‘不允許上傳‘.$extension.‘類型檔案‘); } // 3、判斷是否為允許大小 if($_FILES[‘uploadFile‘][‘size‘] > $max_size){ exit(‘超過了允許上傳到的‘.$max_size.‘位元組‘); } // 4、已到指定位置 $filename = date(‘Ymd‘).rand(1000, 9999); // 產生一個新檔案名稱,防止覆蓋 if(is_uploaded_file($_FILES[‘uploadFile‘][‘tmp_name‘])){ // 判斷是否通過HTTP POST上傳 if(!move_uploaded_file($_FILES[‘uploadFile‘][‘tmp_name‘], $upload_path.$filename.‘.‘.$extension)){ exit(‘無法移動到指定位置‘); } else{ echo ‘檔案上傳成功<br/>‘; echo ‘檔案名稱: ‘.$upload_path.$filename.‘.‘.$extension.‘<br>‘; } } else{ exit(‘檔案未通過合法途徑上傳‘); }複製代碼 本想迅速體驗一把,結果報了個Warning,說時間設定依賴系統...bug總是這麼不期而遇,設定好時間後,再試,perfect! 3.檔案下載 檔案下載就比較簡單了,簡單的檔案下載只需要用一個HTML連結就夠了,使用<a>標籤,href屬性指定資源位置,一點就可。但這種方式只能處理瀏覽器預設無法識別的MIME類型,比如rar、7z等壓縮的資料。 複製代碼<html> <head> <title>donwload file</title> <meta http-equiv="Content-Type" content="text/html"; charset="utf-8" /> </head> <body> <a href="resource/header.txt">header.txt</a><br/> <a href="resource/php.zip">php.zip</a><br/> <a href="resource/pic.ico">pic.ico</a> </body></html>複製代碼 對於這些瀏覽器不認識的類型檔案,點連結,它直接彈框讓你下載,有的瀏覽器甚至直接就下了,那麼對於文本txt、jpg等瀏覽器預設識別的類型的檔案,一點擊則會直接展現在頁面上,比如上面header.txt、pic.ico。如何不展示在頁面上而去下載它們呢,使用header函數。 header函數會通過發送頭資訊告知,請把該檔案當成一個附件,這樣點擊的時候,就也會下載了。 複製代碼<?php $filename = ‘header.txt‘; header(‘Content-Type: text/plain‘); // 類型為普通文本 header(‘Content-Disposition:attachment; filename="$filename"‘); // Content-Disposition:attachment,告訴它這是附件 header(‘Content-Length:‘.filesize($filename)); // 告知檔案大小 readfile($filename); // 讀取檔案直接輸出,便於下載複製代碼
PHP之檔案的鎖定、上傳與下載