2012-04-06 76 views
16

我有一個網頁,我做一個長輪詢我在使用開始本頁這個使用session_write_close()時PHP保存會話;

session_start(); 
session_write_close(); 

因爲:

防止併發只寫腳本可以進行操作在任何時候會話

所以,如果我不這樣做,長輪詢正在運行用戶將無法加載另一個頁面。

因此,從這個輪詢頁面訪問我的會話數據是可能的,但在我的腳本中的某個時刻,我必須將會話保存回服務器,因爲我對其進行了一些更改。

這樣做的方法是什麼?

這將是非常好的,它會是一個辦法做到像

session_write_open(); 
//do stuff 
session_write_close(); 

但session_write_open()不存在!

感謝

+0

後知後覺,對未來的讀者,我建議你使用'的session_set_save_handler()'爲更多最佳實踐,因爲它不涉及任何變通辦法,但修改會話作爲PHP作者似乎有意。我已經發布了一個如何在下面執行此操作的示例。 – 2015-01-16 23:11:56

回答

13

你做出一些改變會話之前,再次調用session_start。進行更改,如果您仍然不想退出呼叫session_write_close。您可以根據需要隨意多次執行此操作。

+0

爲什麼它沒有'session_start'爲我工作?我只是在我的腳本的開頭創建session_write_close,包含很多邏輯,包括會話的更新,並且一切正常,會話更新正確 – 2017-01-19 12:27:07

+0

@VictorBredihin而不看實際的代碼我不知道可能發生了什麼。也許你可以發佈一個新問題。 – Jon 2017-01-26 11:35:18

10

以前的解決方案將創建一個會話ID和餅乾......因爲是我不會用它:

會話創建每次調用session_start時間()。如果您想要 避免多個Cookie,請編寫更好的代碼。多個session_start() 特別是對於同一個腳本中的相同名稱看起來像一個真正的 壞主意。

在這裏看到:https://bugs.php.net/bug.php?id=38104

我要尋找一個解決方案,現在也是一樣,我無法找到一個。我同意那些說這是「錯誤」的人。 您應該可以重新打開一個php會話,但正如您所說session_write_open()不存在...

我在上述線程中找到了解決方法。它涉及在處理請求之後發送一個標頭,手動指定會話ID的cookie。幸運的是,我正在使用自制的前置控制器工作,以便任何分控制器都不會自行發送數據。簡而言之,它適用於我的情況。要使用此功能,您可能只需使用ob_start()ob_get_clean()。這裏是神奇的線:

if (SID) header('Set-Cookie: '.SID.'; path=/', true); 

編輯:看到CMCDragonkai的答案下面,似乎不錯!?

+0

好吧,我不知道cookies,是調用session_start();沒有什麼意義,但我沒有其他解決方案來解決我所知道的問題;)感謝有關錯誤的信息! – 2012-12-05 15:07:20

+0

這可能只是一個缺少的功能... – 2012-12-05 15:07:39

+1

找到了解決方法!編輯答案! – 2012-12-05 16:28:51

3

在測試了Armel Larcier的工作之後。以下是我對此問題提出的解決方案:

ob_start(); 

    session_start(); 
    session_write_close(); 

    session_start(); 
    session_write_close(); 

    session_start(); 
    session_write_close(); 

    session_start(); 
    session_write_close(); 

    if(SID){ 

     $headers = array_unique(headers_list()); 

     $cookie_strings = array(); 

     foreach($headers as $header){ 
      if(preg_match('/^Set-Cookie: (.+)/', $header, $matches)){ 
       $cookie_strings[] = $matches[1]; 
      } 
     } 

     header_remove('Set-Cookie'); 

     foreach($cookie_strings as $cookie){ 
      header('Set-Cookie: ' . $cookie, false); 
     } 

    } 

    ob_flush(); 

這將保留在使用會話之前創建的所有cookie。

順便說一句,你可能希望註冊上面的代碼作爲register_shutdown_function函數。確保在函數前面運行ob_start(),在函數內部運行ob_flush()。

4

其他答案在這裏提出了很好的解決方案。正如@Jon所提到的,訣竅是在想要進行更改之前再次調用session_start()。然後,當您完成更改時,請再次調用session_write_close()。

正如@Armel Larcier所提到的,問題在於PHP嘗試生成新的頭文件並且可能會生成警告(例如,如果您已經向客戶端寫入了非頭文件數據)。當然,你可以簡單地用「@」(@session_start())作爲session_start()的前綴,但有一個更好的方法。

另一個堆棧溢出的問題,通過提供@VolkerK揭示了最好的回答:

session_start(); // first session_start 
... 
session_write_close(); 
... 

ini_set('session.use_only_cookies', false); 
ini_set('session.use_cookies', false); 
//ini_set('session.use_trans_sid', false); //May be necessary in some situations 
ini_set('session.cache_limiter', null); 
session_start(); // second session_start 

這可以防止從PHP再次嘗試發送頭。你甚至可以寫一個輔助函數來包裝中ini_set()函數來使這個多一點方便:

function session_reopen() { 
    ini_set('session.use_only_cookies', false); 
    ini_set('session.use_cookies', false); 
    //ini_set('session.use_trans_sid', false); //May be necessary in some situations 
    ini_set('session.cache_limiter', null); 
    session_start(); //Reopen the (previously closed) session for writing. 
} 

原來那麼相關的提問/回答:https://stackoverflow.com/a/12315542/114558

+1

這是一個很好的發現。 *最好*解決方案將是從不輸出內容塊(總是爲我工作),在這種情況下,你會得到多頭,但他們是無害的。然而這可能不可能,例如,你已經繼承了一些代碼,所以這個解決方法有一個有效的用例。 – Jon 2013-07-30 09:02:18

+0

當你只能使用session_set_save_handler並且完全避免這個問題並且讓它做你想做的事時,這看起來很複雜。 – 2015-01-16 23:08:32

3

這裏所有的答案似乎在說以明顯不打算使用會話方法的方式使用會話方法......即多次呼叫session_start()

PHP網站提供了一個示例SessionHandlerInterface實現,它將像現有會話一樣工作,但不鎖定文件。只是實現他們的示例接口修復了我的鎖定問題,以允許在同一個會話上進行併發連接,而​​不會限制將會話添加到會話的能力。爲了防止一些競爭條件,因爲應用程序的會話不是完全無狀態的,所以我必須設法在不中斷請求的情況下保存會話,以便重要更改可以在更改後立即保存,不太重要的會話更改可以保存在請求結束時。見下面的例子爲使用:

Session::start(); 
echo("<pre>Vars Stored in Session Were:\n");print_r($_SESSION);echo("</pre>"); 

$_SESSION['one'] = 'one'; 
$_SESSION['two'] = 'two'; 
//save won't close session and subsequent request will show 'three' 
Session::save(); 
$_SESSION['three'] = 'three'; 

如果更換Session::start()session_start()Session::save()session_write_close(),你會發現,後續請求將永遠不會打印出的第三個變量...它將會丟失。但是,使用SessionHandler(下面),不會丟失任何數據。

OOP實現需要PHP 5.4+。但是,您可以在舊版本的PHP中提供單獨的回調方法。 See docs

namespace { 
    class Session implements SessionHandlerInterface { 
     /** @var Session */ 
     private static $_instance; 
     private $savePath; 

     public static function start() { 
      if(empty(self::$_instance)) { 
       self::$_instance = new self(); 
       session_set_save_handler(self::$_instance,true); 
       session_start(); 
      } 
     } 
     public static function save() { 
      if(empty(self::$_instance)) { 
       throw new \Exception("You cannot save a session before starting the session"); 
      } 
      self::$_instance->write(session_id(),session_encode()); 
     } 
     public function open($savePath, $sessionName) { 
      $this->savePath = $savePath; 
      if (!is_dir($this->savePath)) { 
       mkdir($this->savePath, 0777); 
      } 

      return true; 
     } 
     public function close() { 
      return true; 
     } 
     public function read($id) { 
      return (string)@file_get_contents("$this->savePath/sess_$id"); 
     } 
     public function write($id, $data) { 
      return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true; 
     } 
     public function destroy($id) { 
      $file = "$this->savePath/sess_$id"; 
      if (file_exists($file)) { 
       unlink($file); 
      } 

      return true; 
     } 
     public function gc($maxlifetime) { 
      foreach (glob("$this->savePath/sess_*") as $file) { 
       if (filemtime($file) + $maxlifetime < time() && file_exists($file)) { 
        unlink($file); 
       } 
      } 

      return true; 
     } 
    }