24

我有一个页面,我在这个页面的开头进行了长时间的轮询,我必须使用这个

session_start();
session_write_close();

因为 :

为了防止并发写入,任何时候只有一个脚本可以对会话进行操作

因此,如果我不这样做并且长轮询正在运行,则用户将无法加载另一个页面。

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

有什么办法呢?

那会很好,这将是一种做类似的事情的方法

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

但是 session_write_open() 不存在!

谢谢

4

5 回答 5

22

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

于 2012-04-06T16:57:51.503 回答
12

以前的解决方案将创建一个会话 id 和 cookie ......我不会按原样使用它:

每次调用 session_start() 时都会创建会话。如果要避免多个cookie,请编写更好的代码。多个 session_start() 尤其是对于同一脚本中的相同名称似乎是一个非常糟糕的主意。

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

我现在也在寻找解决方案,但我找不到。我同意那些说这是一个“错误”的人的观点。您应该能够重新打开一个 php 会话,但正如您所说session_write_open()的那样不存在......

我在上面的线程中找到了解决方法。它涉及在处理请求后发送一个标头,手动指定会话 ID 的 cookie。幸运的是,我正在使用自制的前端控制器,该控制器可以正常工作,因此任何子控制器都不会自行发送数据。简而言之,它在我的情况下非常有效。要使用它,您可能只需要使用ob_start()and ob_get_clean()。这是魔术线:

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

编辑:见下面 CMCDragonkai 的回答,看起来不错!?

于 2012-12-05T14:56:23.213 回答
8

这里的其他答案提供了很好的解决方案。正如@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

于 2013-06-18T20:11:52.237 回答
8

这里的所有答案似乎都在说以它们显然不打算使用的方式使用会话方法......即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 中提供单独的回调方法。请参阅文档

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;
        }
    }
于 2015-01-16T22:22:42.953 回答
4

在测试了 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()。

于 2013-05-15T23:23:30.303 回答