使用session_write_close()时PHP保存会话;

时间:2012-04-06 16:54:48

标签: php session

我有一个页面,我会在本页开头使用这个

进行长时间的轮询
session_start();
session_write_close();

因为:

  

为了防止并发写入,任何时候只有一个脚本可以在会话上运行

因此,如果我没有并且长轮询正在运行,则用户将无法加载另一页。

因此,可以从此轮询页面访问会话中的数据,但在我的脚本中的某些时候我会将会话保存回服务器,因为我对其进行了一些更改。

这样做的方法是什么?

这将非常好,它将成为一种像

这样的方式
session_write_open();
//do stuff
session_write_close();

但是session_write_open()不存在!

由于

5 个答案:

答案 0 :(得分:16)

之前,您对会话进行了一些更改,再次致电session_start。进行更改,如果您仍然不想再次退出呼叫session_write_close。您可以根据需要多次执行此操作。

答案 1 :(得分:10)

之前的解决方案将创建会话ID和Cookie ...我不会按原样使用它:

  

每次调用session_start()时都会创建会话。如果你想   避免多个cookie,编写更好的代码。多个session_start()   特别是对于相同的脚本中的相同名称似乎是真的   坏主意。

见这里:https://bugs.php.net/bug.php?id=38104

我现在正在寻找解决方案而且我找不到解决方案。我同意那些说这是一个" bug"的人。 您应该可以重新打开php会话,但正如您所说session_write_open()不存在...

我在上面的帖子中找到了一个解决方法。它涉及在处理请求后发送一个标题,指定会话ID的cookie。幸运的是,我正在使用自制的前置控制器,这样可以让任何子控制器都不会自己发送数据。 简而言之,它完全适用于我的情况。要使用此功能,您可能只需使用ob_start()ob_get_clean()。这是神奇的界限:

if (SID) header('Set-Cookie: '.SID.'; path=/', true);
编辑:请参阅下面的CMCDragonkai的答案,看起来不错!?

答案 2 :(得分:5)

这里的其他答案提供了非常好的解决方案。正如@Jon所提到的,诀窍是在你想要进行更改之前再次调用session_start()。然后,当您完成更改后,再次调用session_write_close()。

正如@Armel Larcier所提到的,问题在于PHP尝试生成新标头并且可能会生成警告(例如,如果您已经将非标头数据写入客户端)。当然,你可以简单地在session_start()前加上“@”(@session_start()),但是有更好的方法。

由@VolkerK提供的另一个Stack Overflow问题揭示了最佳答案:

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.
}

原始相关的SO问题/答案:https://stackoverflow.com/a/12315542/114558

答案 3 :(得分:4)

这里的所有答案似乎都是说使用会话方法明显不打算使用它们......即不止一次调用session_start()

PHP网站提供了一个示例SessionHandlerInterface实现,它将像现有会话一样工作但不锁定文件。只是实现他们的示例接口修复了我的锁定问题,以允许同一会话上的并发连接,而​​不限制我将vars添加到会话的能力。为了防止某些竞争条件,由于应用程序的会话不是完全无状态的,我确实必须在不关闭它的情况下保存会话中间请求,以便在更改后立即保存重要更改,并且可以节省不太重要的会话变​​量在请求结束时。请参阅以下示例以了解用法:

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;
        }
    }

答案 4 :(得分: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()。