12bet,接着上一篇来讨论跨域通信的问题。我们知道Ajax
是一种页面向服务器请求数据的技术,那么Comet
就是一种服务器向页面推送数据的技术,而且能够让信息以近乎实时地推送到页面上,我们常将它称之为“服务器推送”。12博体育,用一个简单的例子来形容这两项技术的不同就是:你想要查话费,于是你发短信去移动查,然后他会告诉你你的话费剩余多少;12bet,突然有一天你发现你不用再发短信去移动了,移动会主动给你发短信告诉你你的话费剩余多少了。前者就相当于Ajax
,后者就类似于Comet
。
下面我们就一起看看怎么用Comet来实现与跨域服务器的通信吧,这里我们将讨论两种种方法:long polling和http streaming。
Long Polling
long polling也叫长轮询,12bet,很容易联想到那就是还有短轮询或者说轮询啦,是的!12博体育,短轮询就是不断向服务器发送请求,看看有没有新数据,短轮询流程如下图所示:
可以看出这种模式效率不高,12bet,如果大多数情况下没有数据,还是得不断发送请求。对于短轮询的实现,很容易想到就是使用XHR
对象和setTimeout
方法,具体实现大家可以自己去试试。
为了解决上面提到的问题,于是就有了第二种方案-长轮询,长轮询将短轮询颠倒了一下。页面发送请求到服务器,服务器一直保持连接打开,直到有数据可发送。数据发送完毕后,浏览器关闭连接。随后又发送一个新的请求到服务器,这一过程会不断重复。长轮询流程如下图所示:
接下来就来实现吧,先看客户端代码:
var btn = document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/start-polling"), counter = 1;
var poll = function() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "https://localhost:3000/poll/"+counter, true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4) {
var res = document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/result");
res.innerHTML += xhr.responseText + "<br>";
if(++counter <= 5){
poll();
} else {
counter = 1;
}
}
}
xhr.send(null);
}
btn.onclick = function(){
document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/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 https://localhost:3000/poll/3. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://127.0.0.1:3000' is therefore not allowed access.
服务器端我们只是简单的将连接保持1s,然后就返回随机数了,你可以实现更复杂的逻辑,例如实现消息推送,当有消息时再发送消息到客户端,这里你用不用担心连接超时,连接超时之后客户端会重新发送连接。下面就看看效果吧:
Http Streaming
另外一种实现就是Http Streaming,与之前提到的轮询不同的是,Http Streaming在整个页面生命周期内只使用一个Http连接,具体来说就是。浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向浏览器发送数据。Http Streaming的过程如下图所示:
还是一样的方法,上代码:
var btn2 = document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/start-stream");
btn2.onclick = function(){
var client = createClient("https://localhost:3000/stream", function(data){
document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/result2").innerHTML += "Received:"+data+"<br>";
}, function(data){
document.querySelector("https://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/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下,却是如下结果:
为何不是想要的结果,怎么就是一次性输出呢?快找原因啊,找到了。
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'
});
终于得到了我们想要的结果:
在IE里面试过吗
这一次真的就可以了吗?我们去IE下看看吧:
好吧,这是一个安全问题,具体我不清楚,只能找google帮忙了,太好了我找到了我想要的答案。打开“internet选项->安全->信任的站点”,加上https://127.0.0.1:3000
,终于圆满了。
完整代码请看这里