Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

Web通信之:数据流(streaming)

  昨天说了长轮询(long-polling),我们把它比作是了一群人在排队上厕所。被这么多人轮着上,厕所都累了!今天要说的数据流就比较轻松,它没有那样长长的队伍,只有一人、一厕。而且这个人在厕所里一呆就是很久的,不会像长轮询那样一直进进出出。但是服务器是轻松了,开发人员就累了。
  数据流通信,实际上即使和服务器建立一个HTTP连接,然后不断开,服务器只要一直通过那个连接发送数据就可以了。这个方法在兼容性上可不是那么容易搞定的。如果是用XHR对象做传输媒介,能完美兼容的只有Chrome和FireFox。Opera下XHR对象接收到数据不会产生事件,需要用一个计时器去判断是否收到数据。在IE下读取中的XHR对象根本就无法操作,所以在IE下我们必须找到其它方法来替代它。这就是使用IFRAME作为IE的传输媒介的方法,因为IFRAME在传输过程中,数据是可以被读出来的。接着又会遇上一个问题,如果使用IFRAME,浏览器会一直保持在加载状态。这就意味着,网页的图标会一直保持等待图标。这样当然不好,所以我们用了另一种手段让IFRAME加载的时候在IE中不显示加载状态。IE中支持HTMLFile对象,这个对象就相当于一个内存中的Document对象,它会解析文档。所以我们创建一个HTMLFile对象,在里面放置一个IFRAME来连接服务器。这样,各种浏览器就都支持了。现在我们来看代码,为了方便测试,服务器程序的代码我就写成了简单的每秒输出当期时间的程序。下面是代码 <?
//设置文档类型,有些浏览器只解析HTML类型的文档
header('Content-Type:text/html; charset=utf-8');
//设置当前脚本为无超时状态
set_time_limit(0);
//先输出1KB的空格
//浏览器要接收一定量的数据之后才会开始解析
echo str_repeat(' ',1024);
flush();
//死循环
while(1){
  //输出当前时间
  echo date('Y-m-d H:i:s');
  //输出一个分隔符
  echo chr(1);
  //发送到客户都
  flush();
  //等待1秒
  sleep(1);
};
?>
  接着是前端的代码,由于IE和其它浏览器的实现原理不同,所以代码会有点长,不过我都做了比较详细的注释,理解起来也不会太困难。 //获取浏览器信息
var isIE=navigator.userAgent.match(/MSIE (\d)/);
isIE=isIE?isIE[1]:undefined;
var isOpera=/Opera/.test(navigator.userAgent);

//设置参数
var url,splitchar;
url="test.php";
splitChar=String.fromCharCode(1);

//判断浏览器
if(isIE)
  //如果是IE则使用这种方法
  (function(){
    var doc,ifr,itv,o,f;
    //保存当前函数
    f=arguments.callee;
    //创建HTMLFile对象
    doc=new ActiveXObject("HTMLFile");
    //在HTMLFile中创建BODY
    doc.write("<body/>")
    //创建IFRAME
    ifr=doc.createElement("iframe");
    //IFRAME的连接到服务器的地址
    ifr.src=url;
    //把IFRAME放入HTMLFile的BODY中
    doc.body.appendChild(ifr);
    //获取IFRAME中的document对象
    o=ifr.contentWindow.document;
    //创建计时器,循环从IFRAME中读取数据
    itv=setInterval(function(){
      //判断IFRAME的状态
      switch(ifr.readyState){
        case "interactive"://读取数据时
          var i,s;
          //把服务器返回的数据取出
          s=o.body.innerHTML;
          //分割数据,然后循环并操作数据
          s=s.split(splitChar);
          for(i in s)if(s[i])ReceivedData(s[i]);
          //清空IFRAME接收到的数据
          o.body.innerHTML="";
        break;
        case "complete"://服务器断开连接时
          //关闭计时器
          clearInterval(itv);
          //调用当前函数重新连接服务器
          f();
      };
    },1000);
  })();
else
  //如果非IE则使用这种方法
  (function(){
    var xhr,f,p;
    //保存当前函数
    f=arguments.callee;
    //创建XHR对象
    xhr=new XMLHttpRequest;
    //设置数据游标
    //由于服务器一开始会发送1KB的数据
    //所以此处从1024开始
    p=1024;
    //添加爱XHR读取状态改变的事件
    xhr.onreadystatechange=function(){
      switch(xhr.readyState){
        case 3://正在读取数据
          var i,l,s;
          //读取数据
          s=xhr.response;
          //获取数据长度
          l=s.length;
          //从游标位置开始获取数据,并用分割数据
          s=s.slice(p,l-1).split(splitChar);
          //循环并操作数据
          for(i in s)if(s[i])ReceivedData(s[i]);
          //更新游标位置
          p=l;
          //如果缓冲区占太多内存则断开连接
          if(l>10485760)xhr.abort();
        break;
        case 4://与服务器断开
          //重新连接
          setTimeout(f,1000);
      };
    };
    isOpera&&(function(){
      xhr.onreadystatechange();
      setTimeout(arguments.callee,1000);
    })();
    //设置连接参数
    xhr.open("GET",url,true);
    //连接服务器
    xhr.send();
  })();

//收到数据后执行的函数
function ReceivedData(e){
  //把数据输出到BODY中
  var div=document.createElement("div");
  div.appendChild(document.createTextNode(e));
  document.body.appendChild(div);
};
  在IE中为了方法我直接使用了innerHTML来读取,简单的数据可以使用这种方式。不过如果需要输出更复杂的数据就需要自己组织好HTML了,或者把该转义的都转义了也行。其它浏览器中要注意的就是内存,因为XHR对象在连接状态下内存是不能释放的,所以如果需要优化应该往这方面优化。
  数据流的方法是目前HTTP通信中最节省服务器资源的了。如果非要使用HTTP协议开发大型即时通信程序,就可以使用这个方法。当然我更推荐使用AS直接与服务器TCP通信的方法。
网名:
54.144.24.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^