PHP 日本大会 2024

session_write_close

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

session_write_close写入会话数据并结束会话

描述

session_write_close(): 布尔值

结束当前会话并存储会话数据。

通常在脚本终止后存储会话数据,无需调用session_write_close(),但由于会话数据被锁定以防止并发写入,因此任何时候只有一个脚本可以操作会话。当将框架集与会话一起使用时,由于这种锁定,您会发现框架一个接一个地加载。您可以通过在对会话变量的所有更改完成后结束会话来减少加载所有框架所需的时间。

参数

此函数没有参数。

返回值

成功时返回 true,失败时返回 false

变更日志

版本 描述
7.2.0 此函数的返回类型现在为 布尔值。以前,它是 void
添加注释

用户贡献的笔记 13 条笔记

匿名用户
14 年前
如果您仍然启用了会话,那么使用 sleep() 调试任何内容都会很有趣。例如,一个发出 ajax 请求的页面,其中 ajax 请求轮询服务器端事件(并且可能不会立即返回)。

如果 ajax 函数没有执行 session_write_close(),那么您的外部页面将看起来挂起,并且在新标签页中打开其他页面也会停滞。
atul at jreply dot com
11 年前
这里一个很容易忽略的地方 - $_SESSIONS 超全局变量不会因为您调用 session_write_close 而消失。如果在 write_close 调用之后操作超全局变量,则更改在脚本退出时不会保存。同样,调用 session_regenrate_id 会失败。

关闭会话然后操作会话变量并不是许多人有意为之的事情。但是,如果您的会话突然开始出现故障,无法记录更改等,则非常值得检查原因是否为此。
sascha at archinform dot de
7 年前
如果您应用 session_write_close() 来允许来自客户端的并发请求(例如同时进行的 AJAX 调用),如果您启用了输出缓冲(PHP 7+ 中的默认设置),这可能无法解决问题。您必须在 php.ini 中设置 output_buffering = Off,否则会话不会立即关闭。
jcastromail at yahoo dot es
6 年前
为什么这个函数非常重要?我解释一下。

简而言之,会话的工作原理
客户端:php 向客户端发送回一个包含会话 ID 的 cookie。因此,会话在服务器结束处理脚本时结束,而不是在执行 session_write_close() 时结束。因此,除非您对客户使用 ob_flush() 和 flush(),否则使用 session_write_close() 来快速保存客户端的会话是没有用的。

服务器端:它可以更改,但正常行为是在文件中保存会话信息。例如

sess_b2dbfc9ddd789d66da84bf57a62e2000 文件

**此文件通常被锁定**,因此如果两个会话尝试同时打开,则一个会话将被冻结,直到文件解锁。session_write_close() 结束锁定。

例如
<?php
$t1
=microtime(true);
session_start();
sleep(3);
session_write_close();
$t2=microtime(true);
echo
$t2-$t1;
?>

如果我们在两个进程中运行此代码(使用相同的会话,例如两个标签页),则一个将返回 3 秒,而另一个将返回 6 秒。

这是因为第一个进程锁定了会话文件。

但是,更改为
<?php
$t1
=microtime(true);
session_start();
session_write_close();
sleep(3);
$t2=microtime(true);
echo
$t2-$t1;
?>
两个文件都运行 3 秒。

对于 PHP 7.0 及更高版本,我们可以使用 session_start(true); 在第一次读取后自动关闭。

当我们使用相同的会话并行执行许多操作时,此操作对于 AJAX 非常重要
risaac at deadletter dot com
18 年前
如果 session_write_close() 仍然无法足够快地写入会话的解决方法

我发现使用一个 PHP 登录系统,即使 session_write_close() 也没有在我使用 Location: header 传输页面之前设置会话变量。因此,用户将登录,我将创建 $_SESSION 变量,调用 session_write_close(),然后使用 header(Location:...) 传输到安全页面。安全页面将检查会话变量,找不到它们,并强制用户再次登录。第二次登录后,将找到会话,他们可以继续。

我的解决方法是在编写初始登录页面之前使用 0 值创建 $_SESSION 变量。然后我使用登录结果更新会话变量,并使用 header() 函数切换到安全位置。一旦会话变量已经被创建,更新的值就会被快速分配。问题解决了。只需确保安全页面检查 $_SESSION 变量是否存在以及它是否不为 0。
web at murray-mint dot co dot uk
15 年前
如果您将数据保存到会话但发现实际上并没有保存,请检查并确保您没有使用包含管道字符 (|) 的键分配任何数组。这将阻止会话数据被序列化和保存。
sss at activators dot info
13 年前
我一直在使用 MySQL 数据库自定义会话处理程序(不使用 Cookie),并且在让会话数据在我的数据库驱动网站上始终如一地保存时遇到问题

客户有时也必须跨域导航以进行网站管理,因此我有一些特殊的代码来使所有这些相对无缝地发生,并确保每个人的数据都是安全的。

我在设置会话数据的最后一个脚本的最后添加了 session_write_close() 并解决了这个问题。

我不确定为什么,但似乎写入和关闭的调用并不总是被执行(我笨到没搞明白)。

现在已经执行了 `session_write_close()` 调用,我的问题似乎消失了——希望永久消失。

希望这对某些人有所帮助。
atesin > gmail
4年前
我遇到了两个并发脚本的问题,一个登录表单和一个PHP生成的验证码……后者截断了会话(会话文件长度为0),因此由于令牌或验证码错误,登录始终失败……我以为这是一个会话阻塞问题,但事实并非如此。

<?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中不允许使用“<>[\]^`{|}空格”。

但是,管道似乎在会话文件中产生了序列化错误,因此将其更改为另一个无害字符(在我的例子中是下划线)解决了这个问题……尝试避免保留的序列化字符 |:{}";
twicejr
7 年前
您可以轻松地创建一个很酷的聊天框,而无需结合使用框架和子域以及SSE(服务器端事件),例如使用 'while(true){sleep($x)}' 循环。

使用 `session_write_close()` 可以防止会话被锁定(因为请求“永远”不会结束(可能在一两分钟后……否则页面将挂起)。

因此,您可以在共享主机上无需 shell 访问权限即可创建聊天框,您只需要为SSE流创建一个“向所有客户端输出新消息”的功能,并编写几行JavaScript代码。阅读有关SSE的资料。

显然,对于大量客户端,需要良好的缓存或快速的数据库,因为每个人都会产生一个新的流连接。(与推送机制形成对比,推送机制至少需要在共享主机上进行cron作业)。

廉价的聊天框。
ivan at alexandrov dot biz
16年前
我在实现重置密码表单时遇到问题。首先,用户在系统中输入他的登录名或电子邮件。

然后脚本搜索数据库,获取会话数据,并将包含SID的链接发送到注册的电子邮件。链接配置如下:它恢复会话数据并登录到安全界面以更改密码表单。

然后显示一个页面,其中包含有关已发送消息的消息。

问题是ID在三个页面中不唯一,任何人都可以在cookie中看到发送到电子邮件的SID。

我尝试在生成和发送链接之前和之后启动新会话,代码如下:

<?php ....
session_start();
/*从数据库获取用户登录名和电子邮件*/
$user_login = "....";
$user_id = "....."

/*关闭之前的会话*/
session_unlink();
session_destroy();

/*现在生成会话数据的链接*/
session_start();
$_SESSION = $user_login;
$_SESSION = $user_id;
/*在此生成链接:*/
$link = "http://host.com/restore=" . SID . "";
mail (....);

/*关闭包含用户数据的会话*/
session_write_close();

/*并启动一个新会话*/
session_start();
/*然后加载“已发送消息”页面*/
header("Location: /restore/message_sended/");

?>

问题是,即使在 `session_unlink()` 和 `session_write_close()` 之后,SID 也是相同的。`session_start()` 函数只是恢复了之前的会话数据!因此脚本不安全。
然后我在每次 `session_start()` 之后添加了 `session_regenerate_id()` 调用。

<?php ....
session_start();
/*从数据库获取用户登录名和电子邮件*/
$user_login = "....";
$user_id = "....."

/*关闭之前的会话*/
session_unlink();
session_destroy();

/*现在生成会话数据的链接*/
session_start();
session_regenerate_id();// 为发送重新生成SID

$_SESSION = $user_login;
$_SESSION = $user_id;

/*在此生成链接:*/
$link = "http://host.com/restore=" . SID . "";
mail (....);

/*关闭包含用户数据的会话*/
session_write_close();

/*并启动另一个新会话*/
session_start();
session_regenerate_id(); // 重新生成SID
/*然后加载“已发送消息”页面*/
header("Location: /restore/message_sended/");

?>

现在它按需要工作了!我们无法在cookie中看到发送给用户的SID,无论是在生成链接之前还是之后,但数据都保存在具有此ID的会话中。因此只有帐户所有者才能获取它!
editorial at literati dot ca
19年前
进一步参考nakanshi at mailstyle dot com的评论,如果在一个会话中打开多个浏览器窗口/选项卡,并且具有大型会话数据数组,则调用`session_write_close()`后跟`session_start()`似乎会导致问题。我有一个间歇性(并且难以可靠地复制)的问题,即 `session_start()` 从未被调用或没有返回——脚本在写入会话标头之前挂起。我将其归因于尝试过于聪明而不是真正的错误。
prophp at gmail dot com
15 年前
注意,如果您覆盖默认的PHP会话处理并在`write()`函数中使用调试代码,则调试代码只有在运行`session_write_close()`后才会执行。

我尝试了一切,直接从`write()`函数进行文件记录,全局调试变量递增,静态类属性。唯一写入的是会话`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_write_close();
error_log($Session->getDebugInfo(), 3, 'logs/sessions.log');
?>

其中`getDebugInfo`只是返回`self::$debug_Info`。如果没有`session_write_close()`,`sessions.log`将只包含`open()`和`read()`调用。

对许多人来说可能很直观,但我花了几天时间才意识到这一点。希望对您有所帮助!
unspammable-iain at iaindooley dot com
18 年前
众所周知,如果序列化对象,则必须在反序列化之前包含类定义。

我的框架有大量的类文件,在脚本开头包含所有这些文件确实对我的系统(内存和执行时间)造成了影响,因此我切换到在使用它们的每个类文件的顶部包含所需的类,使用`require_once`。

这导致了问题,因为我在脚本执行的开头启动了我的会话,但是我所有的类文件一开始都不在那里!!

所以,在我的特殊“require”函数中,我执行以下操作:

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

与在应用程序开头包含应用程序使用的每个类相比,这是一个小的性能提升。
To Top