传统的 B/S 结构的应用程序, 都是采用客户端拉结束来实现客户端和服务器端的数据交换
本文将通过结合 Ticks(可以参看我的另外一篇文章: 关于 PHP 你可能不知道的-PHP 的事件驱动化设计), 来实现一个服务器推的 PHP 聊天室简单构想
PHPer, 尤其是用过 set_cookie, header 的, 一定见过这样的提示信息: Warning: Cannot modify header information headers already sent by.., 这是因为通过 HTTP 协议通信, 数据包会包含俩个部分, 一个是 Header, 一个是 data 一般来说, 都是先 Header 部分, 在 Heaer 部分指明了 Data 部分的长度, 然后使用 \ r\n\r\n 来表示 header 部分结束, 接下来是 Data 部分
当我们有任何输出的时候, Header 部分就发送了, 这个时候, 你再想 header 函数来改变一些 Header 部分的域信息, 就会得到上面的提示信息
一个简单的办法就是使用 output_buffering 让它来缓存服务器的输出, 不要太早将 Header 部分发给客户端
那么, 如果不使用 output_buffering, 是不是就可以实现, 每当服务器有输出, 就立即发送给客户端呢?
做个如下试验:
- // 设置 php.ini 中 output_buffering=0 或者使用 ob_end_flush() 关闭缓存
- set_time_limit(0);
- for($i=0;$i<10;$i++){
- echo "Now Index is :". $i;
- sleep(1);
- }
结果我们发现, 还是要等到脚本全部执行完以后, 才能一次看到所有的结果
为什么呢?
这是因为我们只是解决了缓存问题, 但是还有一个缓冲问题, PHP 会缓冲程序的输出所以, 这个时候, 我们还需要调用, flush(), 来强制使得 PHP 将所有的程序输出发送给客户端
- set_time_limit(0);
- // 设置 php.ini 中 output_buffering=0
- ob_end_flush();// 关闭缓存
- set_time_limit(0);
- for($i=0;$i<10;$i++){
- echo "Now Index is :". $i;
- flush();
- sleep(1);
- }
现在是不是看到了, 不断有服务器的数据显示出来 (如果看不到, 可以在输出前填充相当数量的占位字符)?
有几个概念之间的关系, 我这里补充以下:
在代码中使用 ob_start(), 就相当于在 php.ini 中使用 output_buffering=on 一样, 使用服务器缓存
在代码中使用 ob_end_flush() 就相当于在 php.ini 中使用 output_buffering = false 一样, 关闭服务器缓存.
基于前面的讨论, 我们就有可能使用 Ticks 来实现, 一个无刷新, 无 ajax 的聊天室: 页面中包含俩个 iframe, 一个是不断获取聊天室的聊天内容, 一个包含用户发表聊天内容的 form. 这样, 在第一个 frame 的脚本中:
- ob_end_clean();// 关闭缓存
- set_time_limit(0);
- ob_implicit_flush(); // 这个语句将强制每当有输出就自动刷新, 相当于在每个 echo 后, 调用 ob_flush()
- $new_mesg = NULL;
- register_tick_function("getNewMesg");
- declare(ticks=1){
- while(1){
- if(!is_null($new_mesg)){
- foreach($new_mesg as $msg){
- echo $msg;
- }
- $new_mesg = null;
- }
- }
- }
- function getNewMesg(){
- // 通过查询数据库, 或者共享内存, 来获取现在的聊天室大厅的内容
- // 返回一个数组, 包含所有的新的聊天内容
- }
这样就实现了一个简单的使用服务器推技术的聊天室的框架
当然, 关于实时输出, 还有一些其他的限制, 比如在 PHP5 手册中讲到的:
个别 web 服务器程序, 特别是 Win32 下的 web 服务器程序, 在发送结果到浏览器之前, 仍然会缓存脚本的输出, 直到程序结束为止
有些 Apache 的模块, 比如 mod_gzip, 可能自己进行输出缓存, 这将导致 flush() 函数产生的结果不会立即被发送到客户端浏览器
甚至浏览器也会在显示之前, 缓存接收到的内容例如 Netscape 浏览器会在接受到换行或 html 标记的开头之前缓存内容, 并且在接受到
标记之前, 不会显示出整个表格
一些版本的 Microsoft Internet Explorer 只有当接受到的 256(甚至更多) 个字节以后才开始显示该页面, 所以必须发送一些额外的空格来让这些浏览器显示页面内容
接下来, 我贴一个很有趣的代码, 有兴趣的同学, 可以试试:
- header("Content-type: multipart/x-mixed-replace;boundary=endofsection");
- print "--endofsection\n";
- $pmt = array("-", "\\", "|", "/" );
- for( $i = 0; $i <10;$i ++ )
- {
- sleep(1);
- print "Content-type: text/plain\n\n";
- print "Part $i".$pmt[$i % 4];
- print "--endofsection\n";
- ob_flush(); // 强制将缓存区的内容输出
- flush(); // 强制将缓冲区的内容发送给客户端
- }
- print "Content-type: text/plain\n\n";
- print "The end\n";
- print "endofsection\n";
使用 firefox 打开, 看看你看到了什么
来源: http://www.bubuko.com/infodetail-2486920.html