PHP Conference Japan 2024

session_write_close

(PHP 4 >= 4.0.4, PHP 5, PHP 7, PHP 8)

session_write_close寫入 session 資料並結束 session

說明

session_write_close(): bool

結束目前的 session 並儲存 session 資料。

Session 資料通常會在您的腳本終止後儲存,無需呼叫 session_write_close(),但由於 session 資料會被鎖定以防止並行寫入,因此任何時間只能有一個腳本操作 session。當搭配 session 使用框架集時,您會因為此鎖定而體驗到框架一個接一個載入。您可以透過在完成對 session 變數的所有變更後立即結束 session 來減少載入所有框架所需的時間。

參數

此函式沒有參數。

回傳值

成功時回傳 true,失敗時回傳 false

變更日誌

版本 說明
7.2.0 此函式的回傳型別現在為 bool。以前,它為 void

參見

新增註解

使用者貢獻的註解 13 則註解

88
匿名
14 年前
如果您有一個仍然啟用的 session,您可以使用 sleep() 來進行有趣的偵錯。例如,一個發出 ajax 請求的頁面,其中 ajax 請求輪詢伺服器端事件(並且可能不會立即返回)。

如果 ajax 函式沒有執行 session_write_close(),那麼您的外部頁面會顯示為掛起,並且在新分頁中開啟其他頁面也會停滯。
33
atul at jreply dot com
11 年前
這裡有個容易發生的陷阱 - $_SESSIONS 超全域變數不會因為您呼叫 session_write_close 而消失。如果在 write_close 呼叫之後操作超全域變數,則在腳本結束時不會儲存變更。同樣地,呼叫 session_regenerate_id 也會失敗。

關閉 session 然後操作 session 變數並不是許多人會故意做的。但是,如果您的 session 突然開始異常運作,無法記錄變更等等,那麼很值得檢查原因是否為此!
12
sascha at archinform dot de
7 年前
如果您應用 session_write_close() 來允許來自用戶端的並行請求(例如同時 AJAX 呼叫),如果您已啟用輸出緩衝(PHP 7+ 中的預設值),則可能無法解決問題。您必須在 php.ini 中設定 output_buffering = Off,否則 session 不會立即關閉。
7
jcastromail at yahoo dot es
6 年前
為什麼這個函式非常重要?我來解釋。

簡而言之,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 非常重要
11
risaac at deadletter dot com
18 年前
如果 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。
10
web at murray-mint dot co dot uk
15 年前
如果你正在將資料儲存到 session 中,卻發現它實際上沒有被儲存,請檢查並確保你沒有使用包含管道符號 (|) 的鍵來賦值任何陣列。這會阻止 session 資料被序列化和儲存。
5
sss at activators dot info
13 年前
我一直在使用 MySQL 資料庫自訂的 session 處理器 (不使用 cookies),並且在我的資料庫驅動網站上,遇到 session 資料無法一致儲存的問題。

客戶有時也需要在網域之間導覽以進行網站管理,所以我寫了一些特殊的程式碼,使這一切相對無縫地發生,並確保每個人的資料安全。

我在設定 session 資料的最後一個腳本的末尾,加入了 session_write_close(),然後問題就解決了。

我不太確定為什麼,但似乎寫入和關閉的呼叫並非總是會被執行 (我沒有聰明到可以找出原因)。

現在 session_write_close() 呼叫被執行了,我的問題似乎已經消失了 - 希望能永遠如此。

希望這對某人有幫助。
1
atesin > gmail
4 年前
我遇到兩個並行腳本的問題,一個是帶有 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 檔案上產生了序列化錯誤,因此更改為另一個無害的字元 (在我的情況下是底線) 就解決了問題... 盡量避免保留的序列化字元 |:{}";
0
twicejr
7 年前
你可以輕鬆製作一個很棒的聊天室,而無需使用框架和子網域,並結合 SSE (伺服器發送事件),例如使用 'while(true){sleep($x)}' 迴圈..

使用 session_write_close() 可防止 session 被鎖定 (因為請求「永遠」不會結束 (可能在一兩分鐘後結束... 否則頁面會掛起))。

因此,你可以在共享主機上製作一個聊天室而無需 shell 存取權限,你只需要為 SSE 流建立一個「輸出所有客戶端新訊息」的函數,並編寫幾行 JavaScript 程式碼。請閱讀關於 SSE 的資訊。

顯然,需要一個良好的快取或具有大量客戶端的快速資料庫,因為每個人都會產生一個新的流連線。(與需要共享主機上至少一個 cron job 的推送機制相比)。

廉價的聊天室。
1
ivan at alexandrov dot biz
16 年前
我在實作還原密碼表單時遇到了問題。首先,使用者在系統中輸入其登入名稱或電子郵件。

然後,腳本搜尋資料庫,取得 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 中。因此,只有帳戶的所有者才能取得它!
1
editorial at literati dot ca
19 年前
針對 nakanishi at mailstyle dot com 的評論,似乎如果你的 session 中有多個瀏覽器視窗/標籤開啟,並且有大量的 session 資料陣列,呼叫 session_write_close() 後面接著 session_start() 會導致問題。我有一個間歇性 (且難以可靠地複製) 的問題,session_start() 永遠不會被呼叫或沒有返回 - 腳本在寫入 session 標頭之前就掛起了。我將此歸因於試圖過於聰明,而不是歸咎於錯誤本身。
0
prophp at gmail dot com
15 年前
請注意,如果你覆寫了預設的 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() 呼叫。

對許多人來說可能很直觀,我花了幾天才意識到。希望對你有幫助!
0
unspammable-iain at iaindooley dot com
18 年前
眾所周知,如果一個物件被序列化,則必須在它被還原序列化之前包含類別定義。

我的框架有大量的類別檔案,在腳本開頭就包含所有這些檔案,真的對我的系統造成了影響 (記憶體和執行時間),因此我改為在每個使用它們的類別檔案頂部,使用 require_once 包含所需的類別。

這造成了問題,因為我在腳本執行開始時就啟動了我的 session,但所有我的類別檔案在開始時都不在那裡!!

因此,在我特殊的「require」函數中,我執行了以下操作

if(!class_exists($to_require))
{
session_write_close();
require_once('path/to/classes/'.$to_require.'.php');
session_start();
}

與應用程式一開始就引入所有使用的類別相比,這是一個效能影響小得多的做法。
To Top