β

试试跨域通信 - Comet

Superlin's Blog 129 阅读
试试跨域通信 - Comet

我们接着上一篇来讨论跨域通信的问题。我们知道 Ajax 是一种页面向服务器请求数据的技术,那么 Comet 就是一种服务器向页面推送数据的技术,而且能够让信息以近乎实时地推送到页面上,我们常将它称之为“服务器推送”。用一个简单的例子来形容这两项技术的不同就是:你想要查话费,于是你发短信去移动查,然后他会告诉你你的话费剩余多少;突然有一天你发现你不用再发短信去移动了,移动会主动给你发短信告诉你你的话费剩余多少了。前者就相当于 Ajax ,后者就类似于 Comet

下面我们就一起看看怎么用Comet来实现与跨域服务器的通信吧,这里我们将讨论两种种方法: long polling http streaming

Long Polling

long polling也叫长轮询,很容易联想到那就是还有短轮询或者说轮询啦,是的!短轮询就是不断向服务器发送请求,看看有没有新数据,短轮询流程如下图所示:

试试跨域通信 - Comet

可以看出这种模式效率不高,如果大多数情况下没有数据,还是得不断发送请求。对于短轮询的实现,很容易想到就是使用 XHR 对象和 setTimeout 方法,具体实现大家可以自己去试试。

为了解决上面提到的问题,于是就有了第二种方案-长轮询,长轮询将短轮询颠倒了一下。页面发送请求到服务器,服务器一直保持连接打开,直到有数据可发送。数据发送完毕后,浏览器关闭连接。随后又发送一个新的请求到服务器,这一过程会不断重复。长轮询流程如下图所示:

试试跨域通信 - Comet

接下来就来实现吧,先看客户端代码:

var btn = document.querySelector("#start-polling"), counter = 1;  
var poll = function() {  
    var xhr = new XMLHttpRequest();
    xhr.open('GET', "http://localhost:3000/poll/"+counter, true);
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4) {
            var res = document.querySelector("#result");
            res.innerHTML += xhr.responseText + "<br>";
            if(++counter <= 5){
                poll();
            } else {
                counter = 1;
            }
        }
    }
    xhr.send(null);
}
btn.onclick = function(){  
    document.querySelector("#result").innerHTML = "";
    poll();
};

我们先向服务器发送请求,等待服务器数据返回,一旦有数据返回便立即处理后,然后立即向服务器发送下一次请求,这里我们只是模拟了5次就中断了连接。下面再来看一下服务器代码:

router.get('/poll/:id', function(req, res, next) {  
  setTimeout(function(){
    res.send("第"+req.params.id+"次获得随机数:"+Math.random());
  }, 1000);
});

上面的代码有问题吗?有,既然是跨域,当然得加上 Access-Control-Allow-Origin (详细介绍见- 使用CORS

res.writeHead(200, {  
    'Access-Control-Allow-Origin': '*'
 });

否则你将会得到如下错误(下一节的Http Streaming中也是一样的):

XMLHttpRequest cannot load http://localhost:3000/poll/3. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:3000' is therefore not allowed access.  

服务器端我们只是简单的将连接保持1s,然后就返回随机数了,你可以实现更复杂的逻辑,例如实现消息推送,当有消息时再发送消息到客户端,这里你用不用担心连接超时,连接超时之后客户端会重新发送连接。下面就看看效果吧:

试试跨域通信 - Comet

Http Streaming

另外一种实现就是Http Streaming,与之前提到的轮询不同的是,Http Streaming在整个页面生命周期内只使用一个Http连接,具体来说就是。浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向浏览器发送数据。Http Streaming的过程如下图所示:

试试跨域通信 - Comet

还是一样的方法,上代码:

var btn2 = document.querySelector("#start-stream");  
btn2.onclick = function(){  
    var client = createClient("http://localhost:3000/stream", function(data){
        document.querySelector("#result2").innerHTML += "Received:"+data+"<br>";
    }, function(data){
        document.querySelector("#result2").innerHTML += "Done!";
    });
}
function createClient(url, process, finished){  
    var xhr = new XMLHttpRequest(), received = 0;
    xhr.open('GET', url, true);
    xhr.onreadystatechange = function(){
        var result;
        if(xhr.readyState == 3){
            result = xhr.responseText.slice(received);
            received += result.length;
            process(result);
        } else if(xhr.readyState == 4) {
            finished(xhr.responseText);
        }
    }
    xhr.send(null);
    return xhr;
}

我们向服务器发送一个请求,之后保持请求,等待服务器发送过来的数据,接收到数据后立即输出,直到数据发送完毕( readyState 为4)。

服务器端每1s向客户端发送一段数据,发送5次后断开连接,服务器代码如下:

res.writeHead(200, {  
    'Access-Control-Allow-Origin': '*'
});
var count = 0;  
var sid = setInterval(function(){  
  res.write(Math.random()+"");
  if(++count == 5){
      clearInterval(sid);
      res.end();
  }
}, 1000);

好吧,该去试验结果了,firefox下ok,可是在chrome下,却是如下结果:

试试跨域通信 - Comet

为何不是想要的结果,怎么就是一次性输出呢?快找原因啊, 找到了

Firefox will show the content immediately. Chrome still seems to buffer (if you write a bunch more content, chrome will show it immediately)

原来是输出的数据太少,chrome会将少量的数据缓存起来,直到数据超过一个chunk,或者请求结束数据输出完毕。显然对于Http Streaming我们不能中断连接,难道只能将每次输出的数据填充到超过一个chunk?例如加好多好多空格,这样似乎不太好。

再来想想这个问题,页面中html是怎么解析的,是下载完毕才解析的吗?是的!那如果服务器提前chunked输出呢?就像我们现在的例子,那就是边下载边解析了, bigpipe 技术就是这个原理,这是不是就意味着如果返回的结果是html就不会buffer了。想到这里,我默默加上了 Content-Type 头部:

res.writeHead(200, {  
    'Access-Control-Allow-Origin': '*',
    'Content-Type': 'text/html'
});

终于得到了我们想要的结果:

试试跨域通信 - Comet

在IE里面试过吗

这一次真的就可以了吗?我们去IE下看看吧:

试试跨域通信 - Comet

好吧,这是一个安全问题,具体我不清楚,只能找google帮忙了,太好了我找到了我想要的 答案 。打开“internet选项->安全->信任的站点”,加上 http://127.0.0.1:3000 ,终于圆满了。

完整代码请看 这里

作者:Superlin's Blog
Front-End, Web, CSS, HTML, Javascript, HTML5, CSS3, Node JS...balaba
原文地址:试试跨域通信 - Comet, 感谢原作者分享。

发表评论