在一些需要浏览器和服务器频繁交流的情况下,如实时分数、市场报价等,我们需要一些适合技术来解决问题。在这个大环境下,Comet被Dojo创始人——Alex Russell发明出来。Comet是一种更高级的Ajax技术。也成为服务器推送。 他适合服务器实时提供新的信息。
Comet有两种方式实现,Ajax长轮询和HTTP流。
Ajax短轮询实现原理很简单,使用一个间歇调用的定时器,定时向服务器发送Ajax请求,服务器将最新的信息送给客户端。
缺点:这种轮询方式,开销很大,大部分时候,可能并没有新信息产生,但浏览器还是会一遍一遍的请求,服务器会一遍一遍的答复。
Ajax长轮询则是将客户与服务器在HTTP层上保持连接。
客户端先向服务器发送一次请求。在没有新的信息产生之前,两者keep-alive。而当有新的信息产生时,服务器response,进行一次问答。客户端收到后,发起第二次请求。
优点:可以减少不必要的开销。所有浏览器都支持轮询技术。
缺点:
1.仍然需要大量的HTTP请求,具有一定的开销
2.仍然是基于HTTP的问答形式。
xhr对象拥有一个readyState属性代表目前状态、onreadystatechange方法在readyState改变时触发。
0:请求未初始化,还没有调用 open()。
1:请求已经建立,但是还没有发送,还没有调用 send()。
2:请求已发送,正在处理中。
3:请求在处理中。
4:响应已完成。
利用这一特点,可以只发送一次HTTP请求。只要服务器配合并不断开连接,在客户端接收到相应的数据时,readyState会周期性的变成3。这样便可以持续收到消息。
let xhr = new XMLHttpRequest()
xhr.open('get', url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 3) {
console.log(xhr.responseText);
}
if (xhr.readyState === 4) {
console.log(`finish!`);
}
}
xhr.send(null)
res.writeHead(200)
res.setHeader("Content-Type","text/event-stream")
setInterval(function () {
res.write("hello")
},1000)
优点:Ajax技术,只发送一次HTTP请求。
缺点:需要服务器的配合。客户端被动接受消息,无法与服务器对话。
Comet技术需要服务器的配合才能良好的发挥作用。
SSE API围绕着Comet技术推出。
在客户端,SSE具有三个事件。open、message、error
var source = new EventSource("see.js");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "
";
};
在服务器也有相应的SSE API,在这里并不深入去说。留给接下来的WebSocket。
优点:相对于WebSocket更轻量。部署在 HTTP协议之上,支持自定义消息类型。
缺点:不支持跨域,只能与创建页面的对象同源。SSE是单向通道,是围绕Comet技术的只读API。
Comet和SSE虽然实现了服务器近乎实时的消息推送,但是也只是单工通信。要实现一些如聊天室的需求,需要服务器和浏览器的全双工通信。一个崭新的协议,WebSocket因此而来。和SSE基于HTTP不同,WebSocket使用自定义的协议。WebSocket 的URL模式与HTTP不同, 服务器在接收到HTTP的upgrade请求头时,http://和https://会升级到ws://和wss://。
WebSocket支持跨域,不需要设置"Access-Control-Allow-Origin"响应头即可建立连接。WebSocket也拥有一个readyState属性代表目前状态、但没有onreadystatechange事件。
0 正在建立连接
1 已经建立
2 正在关闭
3 已经关闭
每一个WebSocket的readyState都是从0开始,到3结束。
它的原生事件很简单,分别为 open、error、close 和 message。
可以这样打开一个websocket。并使用send发送消息。
let socket = new WebSocket('ws://www.wyh.com:8000')
socket.send(JSON.stringify({user,login,data}))
值得注意的是,send只能发送纯文本信息,所以在发送之前进行序列化是有必要的。
下面是封装后的可以登录、点对点通信的webSocket聊天室。
浏览器部分
let socket = new WebSocket('ws://www.***.com:8000')
socket.onmessage = e=>{
console.log(e.data);
}
socket.onopen=e=>{
console.log('建立连接');
}
socket.onerror = e=>{
console.log('err' + e);
}
socket.onclose = e=>{
console.log('close' + e);
}
function sendMessage(user,login,data) {
socket.send(JSON.stringify({user,login,data}))
}
function sendText(user,sendTo,content){
sendMessage(user,0,{sendTo,content})
}
function login(user) {
sendMessage(user,1,{})
}
node服务器部分
const WebSocketServer = require('ws');
const User = new Map() //映射
function heartbeat() { //心跳
this.isAlive = true;
}
module.exports = function (server) {
const webSocket = new WebSocketServer.Server({server: server, perMessageDeflate: true}); //ws 的端口号
webSocket.on('connection', ws => {
ws.on('message', message => { //消息调度中心
let {user, login, data: {sendTo, content,type}} = JSON.parse(message)
if (login) {
User.set(user, ws) //登录
} else {
User.get(sendTo).send(content,type) //发送消息
}
});
ws.on('pong', heartbeat);
setInterval(() => { //向客户端发送ping 确保客户端还‘活着’
webSocket.clients.forEach(client => {
if (client.isAlive === false) return ws.terminate();
client.isAlive = false
client.ping('ping');
});
}, 8000)
});
}