深入理解Comet、SSE、WebSocket

  在一些需要浏览器和服务器频繁交流的情况下,如实时分数、市场报价等,我们需要一些适合技术来解决问题。在这个大环境下,Comet被Dojo创始人——Alex Russell发明出来。Comet是一种更高级的Ajax技术。也成为服务器推送。 他适合服务器实时提供新的信息。

Comet

Comet有两种方式实现,Ajax长轮询和HTTP流。

1.Ajax长轮询和短轮询

Ajax短轮询实现原理很简单,使用一个间歇调用的定时器,定时向服务器发送Ajax请求,服务器将最新的信息送给客户端。
缺点:这种轮询方式,开销很大,大部分时候,可能并没有新信息产生,但浏览器还是会一遍一遍的请求,服务器会一遍一遍的答复。
Ajax长轮询则是将客户与服务器在HTTP层上保持连接。
客户端先向服务器发送一次请求。在没有新的信息产生之前,两者keep-alive。而当有新的信息产生时,服务器response,进行一次问答。客户端收到后,发起第二次请求。
优点:可以减少不必要的开销。所有浏览器都支持轮询技术。
缺点
1.仍然需要大量的HTTP请求,具有一定的开销
2.仍然是基于HTTP的问答形式。

2.HTTP流(HTTP chunk)

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请求。
缺点:需要服务器的配合。客户端被动接受消息,无法与服务器对话。

SSE

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。

WebSocket

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)
    });
}

你可能感兴趣的:(其他)