如果您有一個仍然啟用的 session,您可以使用 sleep() 來進行有趣的偵錯。例如,一個發出 ajax 請求的頁面,其中 ajax 請求輪詢伺服器端事件(並且可能不會立即返回)。
如果 ajax 函式沒有執行 session_write_close(),那麼您的外部頁面會顯示為掛起,並且在新分頁中開啟其他頁面也會停滯。
(PHP 4 >= 4.0.4, PHP 5, PHP 7, PHP 8)
session_write_close — 寫入 session 資料並結束 session
結束目前的 session 並儲存 session 資料。
Session 資料通常會在您的腳本終止後儲存,無需呼叫 session_write_close(),但由於 session 資料會被鎖定以防止並行寫入,因此任何時間只能有一個腳本操作 session。當搭配 session 使用框架集時,您會因為此鎖定而體驗到框架一個接一個載入。您可以透過在完成對 session 變數的所有變更後立即結束 session 來減少載入所有框架所需的時間。
此函式沒有參數。
如果您有一個仍然啟用的 session,您可以使用 sleep() 來進行有趣的偵錯。例如,一個發出 ajax 請求的頁面,其中 ajax 請求輪詢伺服器端事件(並且可能不會立即返回)。
如果 ajax 函式沒有執行 session_write_close(),那麼您的外部頁面會顯示為掛起,並且在新分頁中開啟其他頁面也會停滯。
這裡有個容易發生的陷阱 - $_SESSIONS 超全域變數不會因為您呼叫 session_write_close 而消失。如果在 write_close 呼叫之後操作超全域變數,則在腳本結束時不會儲存變更。同樣地,呼叫 session_regenerate_id 也會失敗。
關閉 session 然後操作 session 變數並不是許多人會故意做的。但是,如果您的 session 突然開始異常運作,無法記錄變更等等,那麼很值得檢查原因是否為此!
如果您應用 session_write_close() 來允許來自用戶端的並行請求(例如同時 AJAX 呼叫),如果您已啟用輸出緩衝(PHP 7+ 中的預設值),則可能無法解決問題。您必須在 php.ini 中設定 output_buffering = Off,否則 session 不會立即關閉。
為什麼這個函式非常重要?我來解釋。
簡而言之,session 的運作方式
用戶端:php 將一個包含 session id 的 cookie 送回給用戶端。因此,session 會在伺服器結束處理腳本時結束,而不是在執行 session_write_close() 時結束。因此,除非您對客戶端執行 ob_flush() 和 flush(),否則使用 session_write_close() 來快速儲存用戶端中的 session 是沒有用的。
伺服器端:可以更改,但正常行為是將 session 資訊儲存在檔案中。例如
sess_b2dbfc9ddd789d66da84bf57a62e2000 檔案
**此檔案通常會被鎖定**,因此如果兩個 session 嘗試同時開啟,則其中一個會凍結,直到該檔案解鎖。session_write_close() 會結束鎖定。
例如
<?php
$t1=microtime(true);
session_start();
sleep(3);
session_write_close();
$t2=microtime(true);
echo $t2-$t1;
?>
如果我們在兩個進程中執行此程式碼(使用相同的 session,例如兩個分頁),則一個會傳回 3 秒,而另一個會傳回 6 秒。
這是因為第一個進程鎖定了 session 檔案。
但是,更改為
<?php
$t1=microtime(true);
session_start();
session_write_close();
sleep(3);
$t2=microtime(true);
echo $t2-$t1;
?>
兩個檔案都會在 3 秒內執行。
對於 PHP 7.0 和更高版本,我們可以使用 session_start(true); 在第一次讀取後自動關閉。
當我們習慣使用相同的 session 並行執行許多操作時,此操作對於 AJAX 非常重要
如果 session_write_close() 仍然無法快速寫入 session 時的解決方法
我發現使用一個 PHP 登入系統,即使 session_write_close() 也沒有在我使用 Location: 標頭傳輸頁面之前設定 session 變數。因此,使用者登入後,我會建立 $_SESSION 變數,呼叫 session_write_close(),然後使用 header(Location:...) 傳輸到安全頁面。安全頁面會檢查 session 變數,但找不到它們,並強制使用者再次登入。第二次登入後,session 會被找到,他們可以繼續。
我的變通方法是在寫初始登入頁面之前,先用 0 值建立 $_SESSION 變數。然後我用登入結果更新 session 變數,並使用 header() 函數切換到安全的位置。一旦 session 變數已經被建立,更新的值會被快速賦予。問題解決。只要確保安全頁面同時檢查 $_SESSION 變數是否存在,且其值不為 0。
如果你正在將資料儲存到 session 中,卻發現它實際上沒有被儲存,請檢查並確保你沒有使用包含管道符號 (|) 的鍵來賦值任何陣列。這會阻止 session 資料被序列化和儲存。
我一直在使用 MySQL 資料庫自訂的 session 處理器 (不使用 cookies),並且在我的資料庫驅動網站上,遇到 session 資料無法一致儲存的問題。
客戶有時也需要在網域之間導覽以進行網站管理,所以我寫了一些特殊的程式碼,使這一切相對無縫地發生,並確保每個人的資料安全。
我在設定 session 資料的最後一個腳本的末尾,加入了 session_write_close(),然後問題就解決了。
我不太確定為什麼,但似乎寫入和關閉的呼叫並非總是會被執行 (我沒有聰明到可以找出原因)。
現在 session_write_close() 呼叫被執行了,我的問題似乎已經消失了 - 希望能永遠如此。
希望這對某人有幫助。
我遇到兩個並行腳本的問題,一個是帶有 PHP 產生的驗證碼的登入表單... 後者截斷了 session (session 檔案長度為 0),因此登入總是因 token 或驗證碼錯誤而失敗... 我以為這是 session 鎖定的問題,但事實並非如此。
<?php // 虛擬碼
@session_start();
$_SESSION['token'] = $token = random_string();
session_write_close();
?><虛擬 HTML>
<form>
<hidden $token/>
名稱:<input name/>
密碼:<input pass/>
驗證碼 <img captcha.php/>:<input captcha/>
<submit/>
</form>
</html>
<?php // captcha.php 虛擬碼
$code = random_string();
if ( request_valid() )
{
@session_start();
$_SESSION['captcha|'.$HTTP_REFERER] = $code;
session_write_close();
}
$img = text_to_img($code);
send_img($img);
?>
我選擇管道符號作為 $_SESSION['captcha|'.$HTTP_REFERER] = $code; 中的分隔符,因為它在 URL 中是被禁止的 "<>[\]^`{|}space。
但似乎管道符號在 session 檔案上產生了序列化錯誤,因此更改為另一個無害的字元 (在我的情況下是底線) 就解決了問題... 盡量避免保留的序列化字元 |:{}";
你可以輕鬆製作一個很棒的聊天室,而無需使用框架和子網域,並結合 SSE (伺服器發送事件),例如使用 'while(true){sleep($x)}' 迴圈..
使用 session_write_close() 可防止 session 被鎖定 (因為請求「永遠」不會結束 (可能在一兩分鐘後結束... 否則頁面會掛起))。
因此,你可以在共享主機上製作一個聊天室而無需 shell 存取權限,你只需要為 SSE 流建立一個「輸出所有客戶端新訊息」的函數,並編寫幾行 JavaScript 程式碼。請閱讀關於 SSE 的資訊。
顯然,需要一個良好的快取或具有大量客戶端的快速資料庫,因為每個人都會產生一個新的流連線。(與需要共享主機上至少一個 cron job 的推送機制相比)。
廉價的聊天室。
我在實作還原密碼表單時遇到了問題。首先,使用者在系統中輸入其登入名稱或電子郵件。
然後,腳本搜尋資料庫,取得 session 資料,並將帶有 SID 的連結傳送到註冊的電子郵件。該連結的配置方式是,它可以還原 session 資料,並將使用者登入到安全介面以變更密碼表單。
然後顯示一個頁面,顯示關於已發送訊息的訊息。
問題是,ID 在三個頁面中不是唯一的,發送到電子郵件的 SID 任何人都可以從 cookie 中看到。
我嘗試在產生程式碼之前和之後開始新的 session
<?php ....
session_start();
/*從資料庫取得使用者登入名稱和電子郵件*/
$user_login = "....";
$user_id = "....."
/*關閉先前的 SESSION*/
session_unlink();
session_destroy();
/*現在產生 SESSION 資料的連結 */
session_start();
$_SESSION = $user_login;
$_SESSION = $user_id;
/*這裡產生連結:*/
$link = "http://host.com/restore=" . SID . "";
mail (....);
/*關閉帶有使用者資料的 SESSION*/
session_write_close();
/*然後啟動新的 SESSION*/
session_start();
/*然後載入「已發送訊息」頁面*/
header("Location: /restore/message_sended/");
?>
問題是,即使在 session_unlink() 和 session_write_close() 之後,SID 也是相同的。session_start() 函數只是還原了先前的 session 資料!!! 因此,腳本並不安全。
然後我在每次 session_start() 之後加入了 session_regenerate_id() 呼叫。
<?php ....
session_start();
/*從資料庫取得使用者登入名稱和電子郵件*/
$user_login = "....";
$user_id = "....."
/*關閉先前的 SESSION*/
session_unlink();
session_destroy();
/*現在產生 SESSION 資料的連結 */
session_start();
session_regenerate_id();// 重新產生要發送的 SID
$_SESSION = $user_login;
$_SESSION = $user_id;
/*這裡產生連結:*/
$link = "http://host.com/restore=" . SID . "";
mail (....);
/*關閉帶有使用者資料的 SESSION*/
session_write_close();
/*然後啟動另一個新的 SESSION*/
session_start();
session_regenerate_id(); // 重新產生 SID
/*然後載入「已發送訊息」頁面*/
header("Location: /restore/message_sended/");
?>
現在它按需工作了! 發送給使用者的 SID,我們無論在產生連結之前還是之後都無法在 cookie 中看到,但資料會以這個 ID 儲存在 session 中。因此,只有帳戶的所有者才能取得它!
針對 nakanishi at mailstyle dot com 的評論,似乎如果你的 session 中有多個瀏覽器視窗/標籤開啟,並且有大量的 session 資料陣列,呼叫 session_write_close() 後面接著 session_start() 會導致問題。我有一個間歇性 (且難以可靠地複製) 的問題,session_start() 永遠不會被呼叫或沒有返回 - 腳本在寫入 session 標頭之前就掛起了。我將此歸因於試圖過於聰明,而不是歸咎於錯誤本身。
請注意,如果你覆寫了預設的 PHP Session 處理,並在 write() 函數內使用除錯程式碼,則除錯程式碼要等到你執行 session_write_close() 才會被執行。
我嘗試了一切,直接從 write() 函數記錄檔案、全域除錯變數遞增、靜態類別屬性。唯一被寫入的是 session 的 open() 和 read() 呼叫。我的除錯程式碼如下所示
<?php
$Session = new Session();
...
class Session() {
public function write($id)
$sql = "UPDATE ... WHERE id=". mysql_real_escape_string($id);
self::$debug_Info .= "session_write sql=$sql";
...
}
# 然後在腳本的最後:
# session 除錯
session_write_close();
error_log($Session->getDebugInfo(), 3, 'logs/sessions.log');
?>
其中 getDebugInfo 只是返回 self::$debug_Info。如果沒有 session_write_close(),sessions.log 將只包含 open() 和 read() 呼叫。
對許多人來說可能很直觀,我花了幾天才意識到。希望對你有幫助!
眾所周知,如果一個物件被序列化,則必須在它被還原序列化之前包含類別定義。
我的框架有大量的類別檔案,在腳本開頭就包含所有這些檔案,真的對我的系統造成了影響 (記憶體和執行時間),因此我改為在每個使用它們的類別檔案頂部,使用 require_once 包含所需的類別。
這造成了問題,因為我在腳本執行開始時就啟動了我的 session,但所有我的類別檔案在開始時都不在那裡!!
因此,在我特殊的「require」函數中,我執行了以下操作
if(!class_exists($to_require))
{
session_write_close();
require_once('path/to/classes/'.$to_require.'.php');
session_start();
}
與應用程式一開始就引入所有使用的類別相比,這是一個效能影響小得多的做法。