服务器推技术简介及php实现服务器推技术的聊天室

IBM:Comet:基于 HTTP 长连接的“服务器推”技术

DEMO1:

  首先是首页,包含一个文本输入和一个显示聊天内容的iframe,还有一个隐藏iframe用来提交form表单:

<?php

//chat.php

header('cache-control: private');

header('Content-Type: text/html; charset=utf-8');

?>

<html>

<script type="text/javascript">

function submitChat(obj) {

obj.submit();

document.getElementsByName('content')[0].value = '';

}

</script>

<iframe src="./chat_content.php" height="300" width="100%"></iframe>

<iframe name="say" height="0" width="0"></iframe>

<form method="POST" target="say" action="./say.php" onsubmit="submitChat(this)">

<input type="text" size="30" name="content" /> <input type="button" value="say"onclick="submitChat(this.form)" />

</form>

</html>

  另外一个就是保存用户提交的聊天内容了,我简单的写一下文本,而且没有做什么锁定,这个只是简易版本:

<?php

$content = trim($_POST['content']);

if ($content) {

$fp = fopen('./chat.txt', 'a');

fwrite($fp, $content . "\n");

fclose($fp);

clearstatcache();

}

?>

  接下来看主要的HTTP长连接部分,也就是chat_content.php文件:

<?php

header('cache-control: private');

header('Content-Type: text/html; charset=utf-8');

 

//测试设置30秒超时,一般会设置比较长时间。

set_time_limit(30);

 

//这一行是为了搞定IE这个BT

echo str_repeat(' ', 256);

 

ob_flush();

flush();

$fp = new SplFileObject('./chat.txt', 'r+');

$line = 0;

$totalLine = 0;

while (!$fp->eof()) {

$fp->current();

$totalLine++;

$fp->next();

}

$fp->seek($totalLine);

$i = $totalLine - 1;

while (true) {

if (!$fp->eof()) {

if ($content = trim($fp->current())) {

echo '<div>';

echo htmlspecialchars($content);

echo "</div>";

flush();

$fp->next();

$i++;

}

} else {

$fp->seek($i - 1);

$fp->next();

}

 

{

//这里可以添加心跳检测后退出循环

}

usleep(1000);

}

?>

  06. 设置一个超时时间,由于要保持HTTP长连接,这个时间肯定要比较长,可能要几个小时吧,上面提到的文章里也有说明,这种HTTP长连接只能打开两个,由于浏览器的限制。另外其实即使你设置了一个永不超时,其实上服务器部分(如Apache)的配置文件也可能对HTTP请求设置了最长等待时间,所以也可能效果会不是你想的,一般默认可能都是15分钟超时。如果有兴趣可以自己尝试修改。

  09. 这里输出了一段空白,主要是手册上已经说明了,IE浏览器在前面256个字符是不会直接输出的,所以我们先随便输出些空白,以便让后面的内容输出来,可能其他浏览器也有其他浏览器的设置,具体可以查看PHP手册的frush函数的说明。接下去11、12行就是强制把这些空白符丢给浏览器输出。

  13. ~ 20. 这里主要是为了计算文件行数,以便从这一行后面开始读内容。

  接下去的while循环就是一个死循环了,就是循环输出文件内容,每次判断是否到达文件末尾,如果有用户写入文件,则当前检测肯定不是文件末尾,就将该行读取出来输出,否则将指针往前移动一行,继续循环,每次等待1000微秒,

  39. 如果一直保持长连接,那么即使客户端断开,服务端也不一定能知道客户端已经断开,所以这里可能还需要做一些心跳记录,比如每个用户保持一个心跳flag,每格几秒更新一下最后心跳时间,当检测最后时间很久没更新后,推出这个死循环,关闭这个HTTP连接。

 

 

DEMO2:

 

传统的B/S结构的应用程序,都是采用\"客户端拉\"结束来实现客户端和服务器端的数据交换。 

本文将通过结合Ticks,来实现一个服务器推的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将所有的程序输出发送给客户端。//设置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_clear();//关闭缓存 

set_time_limit(0); 

ob_implicit_flush(); //这个语句将强制每当有输出就自动刷新,相当于在每个echo后,调用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()函数产生的结果不会立即被发送到客户端浏览器。 [Page]

 

甚至浏览器也会在显示之前,缓存接收到的内容。例如 Netscape 浏览器会在接受到换行或 html 标记的开头之前缓存内容,并且在接受到 </table> 标记之前,不会显示出整个表格。 

 

一些版本的 Microsoft Internet Explorer 只有当接受到的256个字节以后才开始显示该页面,所以必须发送一些额外的空格来让这些浏览器显示页面内容。 

 

 接下来,我贴一个很有趣的代码,有兴趣的同学,可以试试: 

<?php

header('Content-type: multipart/x-mixed-replace;boundary=endofsection'); 

print "endofsection"; 

for( $i = 0; $i <10;$i ++ ) 

        sleep(1); 

        print "Content-type: text/plain "; 

        print "Part $i "; 

        print "--endofsection "; 

        ob_flush(); //强制将缓存区的内容输出 

        flush(); //强制将缓冲区的内容发送给客户端 

  } 

print "Content-type: text plain "; 

print "The end "; 

print "--endofsection-- ";

 

?>

 

  使用firefox打开,看看你看到了什么。 

  这个例子,使用了ob_flush(), 这样可以在代码中控制缓存区内容的输出时机,更加灵活一些。

用PHP的ob_start();控制浏览器cache!

 

Output Control 函数可以让你自由控制脚本中数据的输出。它非常地有用,特别是对于:当你想在数据已经输出后,再输出文件头的情况。输出控制函数不对使用 header() 或 setcookie(), 发送的文件头信息产生影响,只对那些类似于 echo() 和 PHP 代码的数据块有作用。

 

我们先举一个简单的例子,让大家对Output Control有一个大致的印象:

Example 1.

 

CODE<?php

ob_start(); //打开缓冲区

echo \"Hellon\"; //输出

header("location:index.php"); //把浏览器重定向到index.php

ob_end_flush();//输出全部内容到浏览器

?>

 

所有对header()函数有了解的人都知道,这个函数会发送一段文件头给浏览器,但是如果在使用这个函数之前已经有了任何输出(包括空输出,比如空格,回车和换行)就会提示出错。如果我们去掉第一行的ob_start(),再执行此程序,我们会发现得到了一条错误提示:"Header had all ready send by"!但是加上ob_start,就不会提示出错,原因是当打开了缓冲区,echo后面的字符不会输出到浏览器,而是保留在服务器,直到你使用flush或者ob_end_flush才会输出,所以并不会有任何文件头输出的错误!

 

 

一、 相关函数简介:

1、Flush:刷新缓冲区的内容,输出。

函数格式:flush()

说明:这个函数经常使用,效率很高。

2、ob_start :打开输出缓冲区

函数格式:void ob_start(void)

说明:当缓冲区激活时,所有来自PHP程序的非文件头信息均不会发送,而是保存在内部缓冲区。为了输出缓冲区的内容,可以使用ob_end_flush()或flush()输出缓冲区的内容。

3 、ob_get_contents :返回内部缓冲区的内容。

使用方法:string ob_get_contents(void)

说明:这个函数会返回当前缓冲区中的内容,如果输出缓冲区没有激活,则返回 FALSE 。

4、ob_get_length:返回内部缓冲区的长度。

使用方法:int ob_get_length(void)

说明:这个函数会返回当前缓冲区中的长度;和ob_get_contents一样,如果输出缓冲区没有激活。则返回 FALSE。

5、ob_end_flush :发送内部缓冲区的内容到浏览器,并且关闭输出缓冲区。

使用方法:void ob_end_flush(void)

说明:这个函数发送输出缓冲区的内容(如果有的话)。

6、ob_end_clean:删除内部缓冲区的内容,并且关闭内部缓冲区

使用方法:void ob_end_clean(void)

说明:这个函数不会输出内部缓冲区的内容而是把它删除!

7、ob_implicit_flush:打开或关闭绝对刷新

使用方法:void ob_implicit_flush ([int flag])

说明:使用过Perl的人都知道$|=x的意义,这个字符串可以打开/关闭缓冲区,而ob_implicit_flush函数也和那个一样,默认为关闭缓冲区,打开绝对输出后,每个脚本输出都直接发送到浏览器,不再需要调用 flush()

 

 

二、深入了解:

 

1. 关于Flush函数:

这个函数在PHP3中就出现了,是一个效率很高的函数,他有一个非常有用的功能就是刷新browser的cache.我们举一个运行效果非常明显的例子来说明flush.

Example 2.

 

CODE<?php

for($i = 1; $i <= 300; $i++ ) print(" ");

// 这一句话非常关键,cache的结构使得它的内容只有达到一定的大小才能从浏览器里输出

// 换言之,如果cache的内容不达到一定的大小,它是不会在程序执行完毕前输出的。经

// 过测试,我发现这个大小的底限是256个字符长。这意味着cache以后接收的内容都会

// 源源不断的被发送出去。

For($j = 1; $j <= 20; $j++) {

echo $j."

";

flush(); //这一部会使cache新增的内容被挤出去,显示到浏览器上

sleep(1); //让程序"睡"一秒钟,会让你把效果看得更清楚

}

?>

 

 

具体效果你可以到这里看看[url]http://www.php2000.com/~uchinaboy/out.php[/url]

PHP2000的最新的PHP聊天室就是用的这个技术,可惜的是源代码未公开 L

注:如果在程序的首部加入ob_implicit_flush()打开绝对刷新,就可以在程序中不再使用flush(),这样做的好处是:提高效率!

 

2. 关于ob系列函数:

我想先引用我的好朋友y10k的一个例子:

Example 3.

 

比如你用得到服务器和客户端的设置信息,但是这个信息会因为客户端的不同而不同,如果想要保存phpinfo()函数的输出怎么办呢?在没有缓冲区控制之前,可以说一点办法也没有,但是有了缓冲区的控制,我们可以轻松的解决:

CODE<?php

ob_start(); //打开缓冲区

phpinfo(); //使用phpinfo函数

$info=ob_get_contents(); //得到缓冲区的内容并且赋值给$info

$file=fopen(\'info.txt\',\'w\'); //打开文件info.txt

fwrite($file,$info); //写入信息到info.txt

fclose($file); //关闭文件info.txt

?>

 

 

用以上的方法,就可以把不同用户的phpinfo信息保存下来,这在以前恐怕没有办法办到!其实上面就是将一些"过程"转化为"函数"的方法!

或许有人会问:"难道就这个样子吗?还有没有其他用途?"当然有了,比如笔者论坛的PHP 语法加亮显示就和这个有关(PHP默认的语法加亮显示函数会直接输出,不能保存结果,如果在每次调用都显示恐怕会很浪费CPU,笔者的论坛就把语法加亮函数显示的结果用控制缓冲区的方法保留了),大家如果感兴趣的话可以来看看[url]http://www.zphp.com/bbs/[/url]!

 

可能现在大家对ob_start()的功能有了一定的了解,上面的一个例子看似简单,但实际上已经掌握了使用ob_start()的要点。

<1>.使用ob_start打开browser的cache,这样可以保证cache的内容在你调用flush(),ob_end_flush()(或程序执行完毕)之前不会被输出。

<2>.现在的你应该知道你所拥有的优势:可以在任何输出内容后面使用header,setcookie以及session,这是ob_start一个很大的特点;也可以使用ob_start的参数,在cache被写入后,然后自动运行命令,比如ob_start(\"ob_gzhandler\");而我们最常用的做法是用ob_get_contents()得到cache中的内容,然后再进行处理……

<3>.当处理完毕后,我们可以使用各种方法输出,flush(),ob_end_flush(),以及等到程序执行完毕后的自动输出。当然,如果你用的是ob_get_contents(),那么就要你自己控制输出方式了。

 

来,让我们看看能用ob系列函数做些什么……

 

一、 静态模版技术

 

简介:所谓静态模版技术就是通过某种方式,使得用户在client端得到的是由PHP产生的html页面。如果这个html页面不会再被更新,那么当另外的用户再次浏览此页面时,程序将不会再调用PHP以及相关的数据库,对于某些信息量比较大的网站,例如sina,163,sohu。类似这种的技术带来的好处是非常巨大的。

 

我所知道的实现静态输出的有两种办法:

<1>.通过y10k修改的phplib的一个叫template.inc.php类实现。

<2>.使用ob系列函数实现。

对于第一种方法,因为不是这篇文章所要研究的问题,所以不再赘述。

我们现在来看一看第二种方法的具体实现:

Example 4.

 

 

CODE<?php

ob_start();//打开缓冲区

?>

php页面的全部输出

<?

$content = ob_get_contents();//取得php页面输出的全部内容

$fp = fopen("output00001.html", "w"); //创建一个文件,并打开,准备写入

fwrite($fp, $content); //把php页面的内容全部写入output00001.html,然后……

fclose($fp);

?>

 

这样,所谓的静态模版就很容易的被实现了……

 

二、 捕捉输出

 

以上的Example 4.是一种最简单的情况,你还可以在写入前对$content进行操作……

你可以设法捕捉一些关键字,然后去对它进行再处理,比如Example 3.所述的PHP语法高亮显示。个人认为,这个功能是此函数最大的精华所在,它可以解决各种各样的问题,但需要你有足够的想象力……

Example 5.

 

CODE<?

Function run_code($code) {

If($code) {

ob_start();

eval($code);

$contents = ob_get_contents();

ob_end_clean();

}else {

echo "错误!没有输出";

exit();

}

return $contents;

}

 

 

以上这个例子的用途不是很大,不过很典型$code的本身就是一个含有变量的输出页面,而这个例子用eval把$code中的变量替换,然后对输出结果再进行输出捕捉,再一次的进行处理……

 

Example 6. 加快传输

 

 

CODE<?

/*

** Title.........: PHP4 HTTP Compression Speeds up the Web

** Version.......: 1.20

** Author........: catoc <[email]catoc@163.net[/email]>

** Filename......: gzdoc.php

** Last changed..: 18/10/2000

** Requirments...: PHP4 >= 4.0.1

** PHP was configured with --with-zlib[=DIR]

** Notes.........: Dynamic Content Acceleration compresses

** the data transmission data on the fly

** code by sun jin hu (catoc) <[email]catoc@163.net[/email]>

** Most newer browsers since 1998/1999 have

** been equipped to support the HTTP 1.1

** standard known as \"content-encoding.\"

** Essentially the browser indicates to the

** server that it can accept \"content encoding\"

** and if the server is capable it will then

** compress the data and transmit it. The

** browser decompresses it and then renders

** the page.

**

** Modified by John Lim ([email]jlim@natsoft.com.my[/email])

** based on ideas by Sandy McArthur, Jr

** Usage........:

** No space before the beginning of the first \'<?\' tag.

** ------------Start of file----------

** |<?

** | include(\'gzdoc.php\');

** |? >

** |<HTML>

** |... the page ...

** |</HTML>

** |<?

** | gzdocout();

** |? >

** -------------End of file-----------

*/

ob_start();

ob_implicit_flush(0);

function CheckCanGzip(){

global $HTTP_ACCEPT_ENCODING;

if (headers_sent() || connection_timeout() || connection_aborted()){

return 0;

}

if (strpos($HTTP_ACCEPT_ENCODING, \'x-gzip\') !== false) return \"x-gzip\";

if (strpos($HTTP_ACCEPT_ENCODING,\'gzip\') !== false) return \"gzip\";

return 0;

}

/* $level = compression level 0-9, 0=none, 9=max */

function GzDocOut($level=1,$debug=0){

$ENCODING = CheckCanGzip();

if ($ENCODING){

print \"n<!-- Use compress $ENCODING -->n\";

$Contents = ob_get_contents();

ob_end_clean();

if ($debug){

$s = \"<p>Not compress length: \".strlen($Contents);

$s .= \"

Compressed length: \".strlen(gzcompress($Contents,$level));

$Contents .= $s;

}

header(\"Content-Encoding: $ENCODING\");

print \"x1fx8bx08x00x00x00x00x00\";

$Size = strlen($Contents);

$Crc = crc32($Contents);

$Contents = gzcompress($Contents,$level);

$Contents = substr($Contents, 0, strlen($Contents) - 4);

print $Contents;

print pack(\'V\',$Crc);

print pack(\'V\',$Size);

exit;

}else{

ob_end_flush();

exit;

}

}

?>

 

这是catoc的一段很早以前的代码,是在weblogs.com看到的,他利用了zlib的函数,对传输的内容进行了压缩,测试表明,对于10k以上的页面,会产生效果,而且页面越大,效果越明显……

HTML 5.0的未来,特性

 

和以前的版本不同,HTML 5 并非仅仅用来表示 Web 内容,它的使命是将 Web 带入一个成熟的应用平台,在这个平台上,视频,音频,图象,动画,以及同电脑的交互都被标准化。尽管 HTML 5 的实现还有很长的路要走,但 HTML 5 正在改变 Web。

    HTML 最近的一次升级是1999年12月发布的 HTML 4.01。自那以后,发生了很多事。最初的浏览器战争已经结束,Netscape 灰飞烟灭,IE5 作为赢家后来又发展到 IE6, IE7。Mozilla Firefox 从 Netscape 的死灰中诞生,并跃居第二位。苹果和 Google 各自推出自己的浏览器,而小家碧玉的 Opera 仍然嘤嘤嗡嗡地活着,并以推动 Web 标准为己命。我们甚至在手机和游戏机上有了真正的 Web 体验,感谢 Opera,iPhone 以及 Google 即将推出的 Android。

    然而这一切,仅仅让 Web 标准运动变得更加混乱,HTML 5 和其它标准被束之高阁,结果,HTML 5 一直以来都是以草案的面目示人。

    于是,一些公司联合起来,成立了一个叫做 Web Hypertext Application Technology Working Group (Web 超文本应用技术工作组 - WHATWG) 的组织,他们将重新拣起 HTML 5。这个组织独立于 W3C,成员来自 Mozilla, KHTML/Webkit 项目组,Google,Apple,Opera 以及微软。尽管 HTML 5 草案不会在短期内获得认可,但 HTML 5 总算得以延续。

    HTML 5 将带来什么?以下是 HTML 5 草案中最激动人心的部分:

  • 全新的,更合理的 Tag,多媒体对象将不再全部绑定在 object 或 embed Tag 中,而是视频有视频的 Tag,音频有音频的 Tag。
  • 本地数据库。这个功能将内嵌一个本地的 SQL 数据库,以加速交互式搜索,缓存以及索引功能。同时,那些离线 Web 程序也将因此获益匪浅。
  • 不需要插件的富动画。Canvas 对象将给浏览器带来直接在上面绘制矢量图的能力,这意味着我们可以脱离 Flash 和 Silverlight,直接在浏览器中显示图形或动画。一些最新的浏览器,除了 IE,已经开始支持 Canvas。
  • 浏览器中的真正程序。将提供 API 实现浏览器内的编辑,拖放,以及各种图形用户界面的能力。
  • 内容修饰 Tag 将被剔除,而使用 CSS。


理论上讲,HTML 5 是培育新 Web 标准的土壤,让各种设想在他的组织者之间分享,但 HTML 5 目前仍处于试验阶段。

Mozilla 的技术副总裁 Mike Shaver 说,HTML 5 是一个被寄予厚望的概念,它既是 WHATWG 组织的实验田,又是 W3C 的标准之路。

Shaver 认为,Mozilla 的兴趣和 WHATWG 实验相吻合,Mozilla 在 HTML 5 工作组中非常活跃,我们对一些早期的细则进行实验并将成熟的结果提交 W3C。

在过去的几年,Mozilla 随着各种出现的新标准,推出多个富有前瞻性的项目,包括 Prism,一个用于离线运行 Web 程序的系统,以及 Weave,一个数据存储框架。

Shaver 说,HTML 5 运动肇始于对 W3C 的不耐烦,Web 标准中的很多进展都因 W3C 将重点从 HTML 转移到 XML 而停滞不前。

很多基于 XML 架构的新技术被设计出来替代 HTML,Shaver 说,这不是一条正确的道路,人们不应象黑瞎子掰玉米把样一边掰一边丢。

HTML 5 的新实验在 Firefox 以及 基于 Webkit 的 Safari 和 Chrome 浏览器中逐渐得到强化,但仍有不少问题。

Chrome 的开发者 Darin Fisher 说,Chrome 仍在襁褓中时,就不得不面临几个问题,尽管使用的是最新的 Webkit,HTML 5 的本地数据库功能在 Chrome 的初期版本中并没有实现。因为 Chrome 的沙箱机制和 Webkit 的数据库功能有冲突。

而由于 Chrome 属于秘密开发,Chrome 的开发人员也不便参与 Webkit 的开发。

我们要想保守 Chrome 的秘密,就无法参与 Webkit 社区。Fisher 说,我们很希望可以在某些方面给 Webkit 以帮助,我们拥有众多经验丰富的开发者,我们很想知道人们目前遇到的挑战并乐意提供帮助。

随着 Chrome 的发布,Fisher 说他的团队成员有时会和 Webkit 的人一起吃饭,有些人私下里还成了好朋友。Fisher 称,他们迫切地想同其他 Webkit 开发组一起工作解决离线数据库的问题。

Chrome 里面还包含Google 的开源 Gears 技术,用来实现与 HTML 5 类似的离线功能。

Gears 可以看作已有 API 的替代品,Fisher 说,HTML 5 对新浏览器来说是非常好的东西,但绝大多数用户还使用旧浏览器。Gears 可以让那些旧浏览器也获得这样的 API,我们正在为 HTML 5 版 API 提供兼容。

Gears 兼容性非常好,它正成为将 HTML 5 带向人们桌面的另外一条途径。

目前,绝大多数工作由 Apple,Mozilla, Opera, Google 以及 Trolltech 展开。微软在干什么?IE 因其对 Web 标准的迟钝而闻名,更不要说 HTML 5。但 IE8 可能会做出改变。

微 软 IE 平台与 WHAT 工作组主席 Chris Wilson 在邮件中称,我们希望我们现在开始的工作可以在 HTML 工作组创建一套测试系统。Wilson 说,IE 开发组仍然对 HTML 5 的一些提议感到担忧。我觉得工作组的所有成员都会承认我们还有很多事要做。

目前处于 Beta 版的 IE8,已经包含 HTML 5 的诸多新功能。它拥有一个跨文档消息系统,本地存储,以及一些离线事件来检测网络的中断。但还有些功能还未提上议程,如 Canvas。

HTML 5 非常庞大,仍处在开发阶段,我认为浏览器厂商应当尽快达成一致,而每个浏览器的具体实现时间可以自己选择。Web 开发者和浏览器厂商会同意 Wilson 的下面这句话,这确切无疑是一个激动人心的时刻,我们希望看到 Web 成为新的应用平台。

XMLHTTP基础教程

XMLHTTP方法:



open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword)

bstrMethod: 数据传送方式,即GET或POST。用"POST"方式发送数据,可以大到4MB,也可以换为"GET",只能256KB。

 

bstrUrl: 服务网页的URL。

 

varAsync: async: 一个布尔标识,说明请求是否为异步的。如果是异步通信方式(true),客户机就不等待服务器的响应;如果是同步方式(false),客户机就要等到服务器返回消息后才去执行其他操作

bstrUser: 用户名,可省略。
bstrPassword:用户口令,可省略。

send(varBody)
varBody: 指令集。可以是XML格式数据,也可以是字符串,流,或者一个无符号整数数组。也可以省略,让指令通过Open方法的URL参数代入。 发送数据的方式分为同步和异步两种。在异步方式下,数据包一旦发送完毕,就结束Send进程,客户机执行其他的操作;而在同步方式下,客户机要等到服务器 返回确认消息后才结束Send进程。

setRequestHeader(bstrHeader, bstrValue)
bstrHeader:HTTP 头(header)
bstrValue:HTTP 头(header)的值
如果Open方法定义为POST,可以定义表单方式上传:
xmlhttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"

abort
取消当前 HTTP 请求

getAllResponseHeaders
从响应信息中检索所有的标头字段

getResponseHeader
从响应信息正文中获得一个 HTTP 标头值
 

XMLHTTP属性:

onreadystatechange

指定当readyState属性改变时的事件处理句柄

语法

oXMLHttpRequest.onreadystatechange = funcMyHandler;readyState
XMLHTTP对象中的readyState属性能够反映出服务器在处理请求时的进展状况。客户机的程序可以根据这个状态信息设置相应的事件处理方法。属性值及其含义如下表所示: 
值 说明
0 Response对象已经创建,但XML文档上载过程尚未结束
1 XML文档已经装载完毕
2 XML文档已经装载完毕,正在处理中
3 部分XML文档已经解析
4 文档已经解析完毕,客户端可以接受返回消息

responseBody
Variant型 结果返回为无符号整数数组

responseStream
Variant型 结果返回为IStream流
responseText 
将响应信息作为字符串返回

变量,此属性只读,将响应信息作为字符串返回。 XMLHTTP尝试将响应信息解码为Unicode字符串,XMLHTTP默认将响应数据的编码定为UTF-8,如果服务器返回的数据带BOM(byte-order mark),XMLHTTP可以解码任何UCS-2 (big or little endian)或者UCS-4 数据。注意,如果服务器返回的是xml文档,此属性并不处理xml文档中的编码声明。你需要使用responseXML来处理

responseXML object型 结果返回为XML格式数据。 status Long型 服务器返回的HTTP状态码 statusText String型 服务器HTTP响应行状态   附录 (一) HTTP 1.1支持的状态代码 100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分 101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议 200 OK 一切正常,对GET和POST请求的应答文档跟在后面。 201 Created 服务器已经创建了文档,Location头给出了它的URL。 202 Accepted 已经接受请求,但处理尚未完成。 203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝 204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的 205 Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容 206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它 300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。 301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。 302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。 303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取 304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。 305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取 307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。 400 Bad Request 请求出现语法错误。 401 Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。 403 Forbidden 资源不可用。 404 Not Found 无法找到指定位置的资源 405 Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。 406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容 407 Proxy Authentication Required 类似于401,表示客户必须先经过代理服务器的授权。 408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。 409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。 410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。 411 Length Required 服务器不能处理请求,除非客户发送一个Content-Length头。 412 Precondition Failed 请求头中指定的一些前提条件失败 413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头 414 Request URI Too Long URI太长 416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头 500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求 501 Not Implemented 服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求 502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答 503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头 504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答 505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本

hack该不该用

hack是什么呢,它是用来针对浏览器在css解释上出现异常现象时,为了兼容各浏览器所使用的一种非常手段。

个人认为

一、应尽量避免hack的使用

  很多人都存在一种投机取巧的观点——不兼容,用hack。因为这样省时省力。但这样下去,你永远都无法知道为什么这个浏览器比那个浏览器多出10个像素了,越来越多的不知道将导致你对css的迷惑。你将越来越依赖hack。

  其实浏览器都已经很接近标准了,比如很多时候IE中的一些问题,都是由于hasLayout属性产生的,触发hasLayout便能顺利解决问题。又比如双倍距bug,加上display:inline就能解决问题了,并不需要用到hack来取掉那一倍的宽度。

  投机取巧、为了省时省力而使用hack,将使csser对css、对浏览器特性(或者说是漏洞)的理解停滞不前!故,请应尽量避免hack的使用。

二、不要畏惧hack的使用

  经常见到有人高声呼吁:千万不要使用hack,要做无hack安全绿色界面。

  我认为,这种过分极端的观点是错误的,这是一个误区。hack以及私有属性之所以被人挖掘并为高手所研究,正是因为它有其不可替代的价值。

  在一些特殊的情况下,不使用hack根本没有办法解决问题,比如要一个按钮上的文字在各主流浏览器中完美地垂直居中,如果没有hack,你能做 到吗?比如css仿框架,不使用hack,你能做到吗?当然,你会说你不需要做到那么极致,但亲爱的,老板要。但亲爱的,你能做到更好,为什么不呢?

  你说不要用它,那就是因为它有弊端了,忌用hack的人有何顾虑呢?目前我所听到的声音都属于以下三种:

  1. 向后兼容问题(就是说你的页面ie8兼容了,在ie9下搞不好因为你使用的hack而错乱)

  不可否认,类似的悲剧确实存在。ie6向ie7过度的时代,important声明大概害惨了一批人,ie7继承了ie6的很多bug,但却完全支持了用于ie6 hack的important声明,于是用important根本无法区分ie7和ff2。

  但——但——但——在你使用hack的时候去预测一下是否再出现这样的情况,便可知hack用不用得了。简单例举一下:ie6有3px bug,我用“_margin-right:-3px”来解决,这个下划线+属性的写法仅ie6支持,ie7不支持,其他任何浏览器都不支持。按照标准的 发展趋势来看,今后会有浏览器支持吗?今后会还浏览器出现这么极品这么囧的3px bug吗?这样的hack,不过是以ie6本身来制ie6本身,并不会对其他浏览器及其之后的浏览器造成影响。

  2. 如我之前所陈,使用hack可能致使csser依赖hack。

  这样的问题其实完全事在人为,不依赖hack很简单,我不去依赖就行了——尽、量、避、免、使、用、hack!

  3. 无法通过标准验证。

  乖乖,标准验证是为了什么呢?是为了让你所写的样式更加符合标准,你知道如何去标准就行了,浏览器并不标准的时候,我们还要钻破脑袋了去按照标准的方法做事。别傻了孩子,你会把制定标准的人活活气死的。

标签: 浏览器, css, hack, ie6

iframe自适应高度

这篇文章,希望在这两个方面再做一些深入。

可能有人还没接触到这个问题过,先说明一下,什么是自适应高度吧。所谓iframe自适应高度,就是,基于界面美观和交互的考虑,隐藏了 iframe的border和scrollbar,让人看不出它是个iframe。如果iframe始终调用同一个固定高度的页面,我们直接写死 iframe高度就可以了。而如果iframe要切换页面,或者被包含页面要做DOM动态操作,这时候,就需要程序去同步iframe高度和被包含页的实 际高度了。

顺便说下,iframe在迫不得已的时候才去用,它会给前端开发带来太多的麻烦。

传统做法大致有两个:
方法一,在每个被包含页在本身内容加载完毕之后,执行JS取得本页面的高度,然后去同步父页面的iframe高度。
方法二,在主页面iframe的onload事件中执行JS,去取得被包含页的高度内容,然后去同步高度。
在代码维护角度考虑,方法二是优于方法一的,因为方法一,每个被包含页都要去引入一段相同的代码来做这个事情,创建了好多副本。

两个方法都只处理了静的东西,就是只在内容加载的时候执行,如果JS去操作DOM引起的高度变化,都不太方便。

如果在主窗口做一个Interval,不停的来获取被包含页的高度,然后做同步,是不是即方便,又解决了JS操作DOM的问题了呢?答案是肯定的。

Demo页面:主页面 iframe_a.html ,被包含页面 iframe_b.htmiframe_c.html

主页面代码示例:

<iframe id="frame_content" src="iframe_b.html" scrolling="no" frameborder="0"></iframe><script type="text/javascript">
function reinitIframe(){
var iframe = document.getElementById("frame_content");
try{
iframe.height =  iframe.contentWindow.document.documentElement.scrollHeight;
}catch (ex){}
}
window.setInterval("reinitIframe()", 200);
</script>

一直执行,效率会不会有问题?
我做了测试,同时开5个窗口(IE6、IE7、FF、Opera、Safari)执行这个代码,不会对CPU有什么影响,甚至调整到2ms,也没影响(基本维持在0%占用率)。

下面谈谈各浏览器的兼容性问题,如何获取到正确的高度,主要是对body.scrollHeight和 documentElement.scrollHeight两个值得比较。注意本文用的是这个doctype,不同的doctype应该不会影响结果,但 是假如你的页面没有申明doctype,那还是先去加一个吧。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

在主页面追加以下测试代码,以输出这两个值,代码示例:

<div><button onclick="checkHeight()">Check Height</button></div><script type="text/javascript">
function checkHeight() {
var iframe = document.getElementById("frame_content");
var bHeight = iframe.contentWindow.document.body.scrollHeight;
var dHeight = iframe.contentWindow.document.documentElement.scrollHeight;
alert("bHeight:" + bHeight + ", dHeight:" + dHeight);
}
</script>

被加载页面,可以切换一个绝对定位的层,来使页面高度动态改变。如果层展开,则会撑高页面高度。代码示例:

<div><button onclick="toggleOverlay()">Toggle Overlay</button>
</div>
<div style="height:160px;position:relative">
<div id="overlay" style="position:absolute;width:280px;height:280px;display:none;"></div>
</div>
<script type="text/javascript">
function toggleOverlay() {
var overlay = document.getElementById('overlay');
overlay.style.display = (overlay.style.display == 'none') ? 'block' : 'none';
}
</script>

下面列出以上代码在各浏览器的测试值:
(bHeight = body.scrollHeight, dHeight = documentElement.scrollHeight, 红色 = 错误值, 绿色 = 正确值)

/ 层隐藏时 层展开时
bHeight dHeight bHeight dHeight
IE6184184184303
IE7184184184303
FF184184184303
Opera181181300300
Safari184184303184

暂且无视Opera比别人少3像素的问题…可以看出,如果没有绝对定位的东西,两个值是相等的,取哪个都无所谓。
但是如果有,那么各个浏览器的表现不太相同,单取哪个值都不对。但可以找到了一条规律,那就是取两个值得最大值可以兼容各浏览器。所以我们的主页面代码就要改造成这样了:

function reinitIframe(){var iframe = document.getElementById("frame_content");
    try{
    var bHeight = iframe.contentWindow.document.body.scrollHeight;
    var dHeight = iframe.contentWindow.document.documentElement.scrollHeight;
    var height = Math.max(bHeight, dHeight);
    iframe.height =  height;
    }catch (ex){}
    }
    window.setInterval("reinitIframe()", 200);

这样子,基本解决了兼容性问题。顺便说下,不光绝对定位的层会影响到值,float也会导致两个值的差异。

如 果你演示Demo后,会发现,除了IE,其他浏览器中,当层展开后再隐藏,取到的高度值还是维持在展开的高度303,而非隐藏回去的真正值184,就是说 长高了之后缩不回去了。这个现象在不同被包含页面之间做切换也会发生,当从高的页面切换到矮页面的时候,取到的高度还是那个高的值。
可以归纳为,当iframe窗体高度高于文档实际高度的时候,高度取的是窗体高度,而当窗体高度低于实际文档高度时,取的是文档实际高度。因此,要想办法 在同步高度之前把高度设置到一个比实际文档低的值。所以,在iframe的添加 onload=”this.height=100″,让页面加载的时候先缩到足够矮,然后再同步到一样的高度。
这个值,在实际应用中决定,足够矮但又不能太矮,否则在FF等浏览器里会有很明显的闪烁。DOM操作的时候主页面无法监听到,只能DOM操作完了之后把高度变小了。
在我的一个实际项目中,在成本和收益之间权衡,我并没有做这个事情,因为每个DOM函数中都要插入这个代码,代价太高,其实层缩回去不缩掉也不是那么致命。包括Demo里,也没有去做这个事情。如果读者有更好的方法,请告诉我。

这是最终的主页面的代码:

<iframe id="frame_content" src="iframe_b.html" scrolling="no" frameborder="0" onload="this.height=100"></iframe>
    <script type="text/javascript">
    function reinitIframe(){
    var iframe = document.getElementById("frame_content");
    try{
    var bHeight = iframe.contentWindow.document.body.scrollHeight;
    var dHeight = iframe.contentWindow.document.documentElement.scrollHeight;
    var height = Math.max(bHeight, dHeight);
    iframe.height =  height;
    }catch (ex){}
    }
    window.setInterval("reinitIframe()", 200);
    </script>