如果您有一个仍在活动的会话,那么在其中包含 sleep() 的任何东西进行调试都会很有趣。例如,一个发出 AJAX 请求的页面,其中 AJAX 请求轮询服务器端事件(可能不会立即返回)。
如果 AJAX 函数没有执行 session_write_close(),那么您的外部页面将似乎挂起,并且在新标签页中打开其他页面也会停滞。
(PHP 4 >= 4.0.4, PHP 5, PHP 7, PHP 8)
session_write_close — 写入会话数据并结束会话
结束当前会话并存储会话数据。
会话数据通常在您的脚本终止后存储,无需调用 session_write_close(),但由于会话数据被锁定以防止并发写入,因此一次只有一个脚本可以操作一个会话。当使用框架集和会话时,您将体验到框架逐个加载,这是由于这种锁定造成的。您可以通过在对会话变量的所有更改完成后结束会话来减少加载所有框架所需的时间。
此函数没有参数。
如果您有一个仍在活动的会话,那么在其中包含 sleep() 的任何东西进行调试都会很有趣。例如,一个发出 AJAX 请求的页面,其中 AJAX 请求轮询服务器端事件(可能不会立即返回)。
如果 AJAX 函数没有执行 session_write_close(),那么您的外部页面将似乎挂起,并且在新标签页中打开其他页面也会停滞。
这里有一个容易遇到的陷阱 - $_SESSIONS 超级全局变量不会因为您调用 session_write_close 而消失。如果在 write_close 调用之后,您操作了超级全局变量,那么当脚本退出时,这些更改将不会保存。同样,对 session_regenrate_id 的调用也会失败。
关闭会话然后操作会话变量并非许多人有意为之。但是,如果您的会话突然开始出现故障,无法记录更改等,那么值得检查一下原因是否就是这个!
如果您应用 session_write_close() 来允许来自客户端的并发请求(例如同时的 AJAX 调用),那么如果启用了输出缓冲(PHP 7+ 中的默认设置),这可能无法解决问题。您必须在 php.ini 中将 output_buffering 设置为 Off,否则会话不会立即关闭。
为什么此函数非常重要?我解释一下。
简而言之,会话的工作原理
客户端: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 非常重要,因为我们过去常常使用相同的会话并行执行许多操作。
如果 session_write_close() 仍然不能足够快地写入会话,则可以使用以下方法
我发现,在一个 PHP 登录系统中,即使 session_write_close() 也无法在我使用 Location: 标头传输页面之前设置会话变量。因此用户会登录,我会创建 $_SESSION 变量,调用 session_write_close(),然后使用 header(Location:...) 传输到安全页面。安全页面会检查会话变量,但找不到它们,并强制用户再次登录。第二次登录后,将找到会话,他们就可以继续。
我的解决方法是在写入初始登录页面之前使用 0 值创建 $_SESSION 变量。然后,我使用登录结果更新会话变量,并使用 header() 函数切换到安全位置。一旦会话变量已经创建,更新的值就会很快被分配。问题解决。请确保安全页面同时检查 $_SESSION 变量是否存在以及它是否不为 0。
如果您正在将数据保存到会话,但发现它实际上没有被保存,请检查并确保您没有使用包含管道字符 (|) 的键分配任何数组。这将阻止会话数据被序列化和保存。
我一直在使用 MySQL 数据库自定义会话处理程序(不使用 Cookie),并且在让会话数据在我的数据库驱动网站上始终保存方面遇到了问题
客户有时还需要跨域导航以进行网站管理,因此我有一些特殊代码来使所有这些操作相对无缝地进行,并确保每个人的数据都是安全的。
我在设置会话数据的最后一个脚本的末尾添加了 session_write_close(),并解决了问题。
我不确定为什么,但似乎写和关闭的调用并不总是会被执行(我还不够聪明,无法弄清楚)
现在,session_write_close() 调用正在被执行,我的问题似乎消失了 - 希望是永远消失了。
希望这能帮到某人。
我在两个并发脚本中遇到了麻烦,一个登录表单带有 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 中被禁止使用 “<>[\]^`{|}space
但看起来管道符号在会话文件中造成了序列化错误,所以改用另一个无害字符(我这里用的是下划线)解决了这个问题... 尽量避免使用保留的序列化字符 |:{}";
注意,如果你覆盖了默认的 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() 调用。
可能对很多人来说很直观,但我花了几天时间才意识到。希望对大家有所帮助!
我遇到了实现密码重置表单的问题。首先用户在系统中输入他们的登录名或电子邮件。
然后脚本搜索数据库,获取会话数据,并发送包含 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/");
?>
现在它按预期工作了!发送给用户的 SID 我们在 cookie 中看不到,无论是在生成链接之前还是之后,但数据都保存在具有此 ID 的会话中。因此只有帐户所有者才能获取它!
继 nakanishi at mailstyle dot com 的评论之后,似乎如果在会话中打开多个浏览器窗口/选项卡,并且拥有大型会话数据数组,则调用 session_write_close() 之后再调用 session_start() 会导致问题。我遇到了一个间歇性(并且难以可靠地复制)的问题,session_start() 从未被调用或没有返回 - 脚本在写入会话头之前挂起。我认为这是由于尝试过于聪明,而不是真正的错误。
你可以很容易地创建一个很酷的聊天框,而无需使用框架和子域与 SSE(服务器端事件)结合使用,例如使用 “while(true){sleep($x)}” 循环。
使用 session_write_close() 可以防止会话被锁定(因为请求“永远”不会结束(可能在一两分钟后.. 否则页面会挂起)。
所以你可以在共享主机上创建聊天框,而无需 shell 访问权限,你只需要为 SSE 流创建一个“输出所有客户端的新消息”函数,并编写几行 javascript 代码。了解 SSE。
显然需要一个好的缓存或快速数据库,因为每个用户都会生成一个新的流连接。(与推送机制形成对比,推送机制至少需要在共享主机上创建一个 cron 作业)。
便宜的聊天框。
众所周知,如果一个对象被序列化,那么它的类定义必须在反序列化之前包含。
我的框架有大量的类文件,在脚本开头包含所有这些文件确实对我的系统造成了很大的负担(内存和执行时间),所以我改为在每个使用它们的类文件开头使用 require_once 包含所需的类。
这会导致问题,因为我在脚本执行的开始就启动了会话,但是我的所有类文件在开始时都没有存在!!
因此在我的特殊“require”函数中,我执行以下操作
if(!class_exists($to_require))
{
session_write_close();
require_once('path/to/classes/'.$to_require.'.php');
session_start();
}
与在应用程序开头包含应用程序使用的所有类相比,这种方法的性能损耗要小得多。
我遇到了这里许多人遇到的同样的问题,就是在进行 header location 重定向之前设置会话数据,然后会话数据就不存在了。我尝试了这里每个人说过的所有方法,但他们的组合都没有奏效。最后对我有效的解决方法是在 header() 和 die() 调用之前执行 session_regenerate_id(true) 调用。
session_regenerate_id(true);
header('location: blah blah');
die();
如果没有 regenerate id 调用,write close 似乎什么也没做。session_write_close() 似乎完全不起作用。它本身肯定没有解决我的问题。
这是 PHP 会话中一个相当恼人的问题,我以前从未遇到过。我把我的会话存储到 /dev/shm(即 RAM)中,所以文件 IO 阻塞不可能是问题。现在我担心其他一些会话数据可能在 header() location 更改之前没有更新,这在任何 Web 应用程序中都是非常重要和常见的。
session_write_close() 在自动将文件上传给用户(强制下载而不是链接)时,对我来说是救星。如果文件很大,并且由于 session_start() 不允许使用 session_start() 的另一个页面继续执行,直到它完成,所以我一次只能上传一个文件。通过在开始文件上传之前使用 session_write_close(),我的用户现在可以同时下载任意数量的大文件。示例
<?
session_start();
/* 在这里进行会话操作;安全;日志记录;等等。 */
session_write_close();
/* 现在写出请求的文件。 */
header("Content-type: audio/x-mpeg"); /* 或任何其他类型 */
header("Content-Disposition: attachment; filename=" . $filename);
header("Content-Length: " . $filesize);
header("Content-Transfer-Encoding: binary\n\n");
header("Pragma: no-cache");
header("Expires: 0");
$file_contents = file_get_contents($filepath);
print($file_contents);
?>
注意从 Ajax 页面调用 PHP 脚本时使用 session_write_close:这不会“完成”会话数据的写入,实际上它会重置它!
当尝试在 Windows 2003 Server 上与 WAMP 一起使用 exec 时,你可能会遇到服务器停止响应你的请求的情况。
通过在每个 exec 之前调用 session_write_close,可以解决你的问题。
希望这能帮到某些人!
确保你在 session_write_close() 之后再次调用 session_start(),如果你依赖 SID 重写。否则它不会被重写。
记住:具有私有和/或受保护数据成员的对象不能被 PHP 正确序列化。如果你发现你的 $_SESSION 中缺少一些对象,请确保你在应用程序中考虑了这个缺陷。
我在会话数据方面遇到了类似的问题。
我随机地丢失了会话数据,没有任何模式。我甚至没有使用 header(location:... ) 函数。
尝试了
在 php.ini 中将 "session.use_cookies" 设置为 1
session-write-close()
ob_start() / ob_end_flush()
无论我做了什么,它都没有用。
最后,当我将 "session.use_only_cookies" 设置为 1 时,问题解决了。我不再丢失任何会话。
与 cenaculo at netcabo dot pt、bkatz at usefulengineering dot com 和 editorial at literati dot ca 关于确保你 session_write_close() 的说法类似,如果你使用输出缓冲,不要忘记 ob_end_flush()。
当我尝试从一个页面导航到另一个页面,而该页面在 Iframe 或单独窗口中流式传输内容时,我一直遇到奇怪的挂起问题。
而不是
<?
ob_start();
session_start();
/* 在这里进行会话操作;安全;日志记录;等等。 */
session_write_close();
/* 现在写出请求的文件。 */
header("Content-type: audio/x-mpeg"); /* 或任何其他类型 */
header("Content-Disposition: attachment; filename=" . $filename);
header("Content-Length: " . $filesize);
header("Content-Transfer-Encoding: binary\n\n");
header("Pragma: no-cache");
header("Expires: 0");
$file_contents = file_get_contents($filepath);
print($file_contents);
?>
做
<?
ob_start();
session_start();
/* 在这里进行会话操作;安全;日志记录;等等。 */
session_write_close();
/* 确保数据真正被刷新到浏览器 */
ob_end_flush();
/* 现在写出请求的文件。 */
header("Content-type: audio/x-mpeg"); /* 或任何其他类型 */
header("Content-Disposition: attachment; filename=" . $filename);
header("Content-Length: " . $filesize);
header("Content-Transfer-Encoding: binary\n\n");
header("Pragma: no-cache");
header("Expires: 0");
$file_contents = file_get_contents($filepath);
print($file_contents);
?>
我的会话搞砸了,因为我同时使用了 2 个不同的会话 ID
- 1 个 PHPSESSID cookie 用于 domain.com
- 1 个 PHPSESSID cookie 用于 www.domain.com
这解决了它
// 在每个页面的开头...
session_set_cookie_params (1800,"/","domain.com");
session_start();
此函数对于强制序列化会话数据很有用,但如果它在每个 session_start() 调用中被调用超过一次,它会引入难以追踪的错误。因为它没有返回值或引发异常,所以不会有任何迹象表明序列化失败,代码将正常继续。只有当用户访问依赖于未保存会话数据的页面时,才会出现任何失败的迹象。
据我所知,这会影响默认和自定义会话处理函数。
<?
session_start();
$_SESSION['foo'] = 'box';
session_write_close();
if (array_key_exists('visited',$_SESSION) {
echo "欢迎回来!\n"; # 永远不会发生
} else {
echo "我不认识你。\n";
$_SESSION['visited'] = true;
session_write_close();
}
?>
对于在使用 header("Location:...") 时出现的会话问题,我发现 session_write_close() 在我的 IIS 服务器上使用 CGI 模式下的 PHP 时对我不起作用。问题是 PHPSESSID cookie 永远不会被设置,所以我手动设置了它
header("Set-Cookie: PHPSESSID=" . session_id() . "; path=/");
这样对我有效!
如果你正在尝试使用一个为特定应用程序设计的更大代码库......并且它实现了一些自定义会话保存处理程序,那么如果这些处理程序妨碍了你,似乎没有办法将它们重置回默认的 PHP 状态。我的解决方法
session_write_close(); // 在页面顶部关闭会话 :)
使用 SQL Server 数据库和 Windows Server 2003,如果你在 header() 调用之前使用 session_write_close(),并且它似乎仍然冻结了你的会话,请检查确保你不在事务的中间。
我注意到,通过启动事务、执行一些查询、尝试写入关闭会话,然后在没有回滚或提交该事务的情况下重定向,你的会话可能会冻结。就像你尝试在没有先调用 session_write_close() 的情况下使用 header() 重定向时所遇到的情况一样。
当服务器收到多个请求时,第 2 个请求对会话变量进行的更改(在第 1 个请求休眠时)将不可用于第 1 个请求,因为它已应用了 session_write_close()
<?php
session_start();
// 每个请求发送自己的值
$sign = filter_input(INPUT_GET, 'sign', FILTER_SANITIZE_STRING);
$_SESSION['sign'] = $sign;
session_write_close();
// 在第 1 个请求的休眠期间,第 2 个请求到达,带有不同的 $sign 值
sleep(3);
// 每个请求将显示不同的值,即使 $_SESSION['sign'] 在第 1 个请求到达此行之前被第 2 个请求更改
echo $_SESSION['sign'];
?>
解决方案:在 echo 之前使用 session_start()
<?php
session_start();
// 每个请求发送自己的值
$sign = filter_input(INPUT_POST, 'sign', FILTER_SANITIZE_STRING);
$_SESSION['sign'] = $sign;
session_write_close();
// 在第 1 个请求的休眠期间,第 2 个请求到达,带有不同的 $sign 值
sleep(3);
/** 这将解决问题 **/
session_start();
// 现在两个请求都显示相同的值,即最新的值,由第 2 个请求更改
echo $_SESSION['sign'];
// 如果你计划在此后编写更多代码,你可能需要应用 session_write_close()
?>