浅谈 Comet、SSE、WebSocket

浅谈 Comet、SSE、WebSocket_第1张图片

前言

对于前端开发者,我们只做两件事,获取数据,和把数据展示出来。在不同业务场景下,我们需要不同的通信方式和后端交流。在最早的刀耕火种年代,那时候还没有Ajax,我们只能通过URL请求资源,表单提交数据。后来随着大前端的发展以及业务需要,越来越多的通信技术被发明出来。

Comet

在一些需要浏览器和服务器频繁交流的情况下,如实时分数、市场报价等,我们需要一些适合技术来解决问题。在这个大环境下,Comet被Dojo创始人Alex Russell发明出来。Comet基于Ajax技术,是一种高级用法。它适合服务器实时推送新数据的业务。Comet有两种方式实现,Ajax长轮询和HTTP流。

1.Ajax长轮询和短轮询

「Ajax短轮询」实现原理很简单,使用一个间歇调用的定时器,定时向服务器发送Ajax请求,服务器将最新的信息送给客户端。缺点:这种轮询方式,开销很大,大部分时候,可能并没有新信息产生,但浏览器还是会一遍一遍的请求,服务器会一遍一遍的答复。

「Ajax长轮询」则是将客户与服务器在更新数据之前保持连接状态,也就是客户端暂不答复,客户端先向服务器发送一次http请求。在没有新的信息产生之前,两者暂时保持连接状态。而当有新的信息产生时,服务器response,进行一次问答。此次连接断开,客户端收到后,再次发起请求。优点:可以减少不必要的开销。所有浏览器都支持轮询技术。缺点:1.仍然需要大量的HTTP请求,具有一定的开销2.仍然是基于HTTP的问答形式,只能半双工通信(一方发送数据时,另一方不能同时发送)

很多初学者会混淆长轮询和http1.1的「keep-alive」这两个概念。实际上并没有直接关系。

2.HTTP流(HTTP chunk)

xhr对象拥有一个readyState属性代表目前状态、onreadystatechange方法在readyState改变时触发。

  • 请求未初始化,还没有调用 open()。

  • 请求已经建立,但是还没有发送,还没有调用 send()。

  • 请求已发送,正在处理中。

  • 请求在处理中。

  • 响应已完成。

利用这一特点,可以只发送一次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)

node配合推送(SSE技术)

    res.writeHead(200)
    res.setHeader('Content-Type','text/event-stream')
    res.setHeader('Cache-Control','no-cache')
    res.setHeader('Connection','keep-alive')
    setInterval(function () {
        res.write("hello")
    },1000)

优点:基于Ajax技术支持性良好。保持一个TCP长连接,开销较轮询有较大改善。缺点:需要服务器SSE技术的配合。客户端被动接受消息,无法与服务器对话。

SSE

围绕着SSE技术,前端也推出了SSE API。在客户端,SSE具有三个事件。open、message、error

var source = new EventSource("see.js");
source.onmessage = function(event) {
  document.getElementById("result").innerHTML += event.data + "
"; }; source.onerror = function(err) {   console.log(err) }; source.onopen = function() {   console.log('SSE opened!') };

优点:相对于WebSocket更轻量。部署在 HTTP协议之上,支持自定义消息类型。缺点:不支持跨域,只能与创建页面的对象同源。SSE是单向通道,是围绕Comet技术的只读API。

服务器相应的SSE技术(见上例),在这里并不深入。空间留给接下来的WebSocket。

WebSocket

Comet和SSE虽然实现了服务器近乎实时的消息推送,但是也只能单工通信或半双工通信,适合对低延迟要求不太高的场景(如站内信)。如果要实现一些如聊天室、对战游戏的需求,需要服务器和浏览器的「全双工通信」。一个崭新的协议,「WebSocket」因此而来。和SSE基于HTTP不同,WebSocket使用全新的协议。WebSocket的URL模式与HTTP不同,服务器在接收到HTTP的「upgrade」请求头时,http://和https://会升级到ws://和wss://。

WebSocket支持跨域,不需要像http那样把响应头设置"Access-Control-Allow-Origin"。WebSocket拥有一个readyState属性代表目前状态。

0 正在建立连接
1 已经建立
2 正在关闭
3 已经关闭

每一个WebSocket的readyState都是从0开始,到3结束。它的原生事件很简单,分别为 open、error、close 和 message。可以这样打开一个websocket。并使用send发送消息。

 let socket = new WebSocket('ws://www.wyh.com:8000')
 socket.onopen = () => {
     console.log('建立webSocket连接');
     socket.send(JSON.stringify({user,data}))
 }
 socket.onclose = () => {
      console.log('断开webSocket连接');
 }
 socket.onerror = () => {
      console.log('建立webSocket连接');
 }
 socket.onmessage = (msg) =>{
     let {user,data} = JSON.parse(msg.data)
 }

值得注意的是,send只能发送「纯文本」,所以在发送JavaScript对象之前进行序列化是有必要的。

node后端(心跳监听客户端是否存活)

const WebSocketServer = require('ws');
const eventTopic = require('./eventTopic')

module.exports = function (server) {
    const webSocket = new WebSocketServer.Server({server: server, perMessageDeflate: true}); //ws 的端口号
    webSocket.on('connection', ws => {
        
        ws.on('message', message => {   //消息调度中心
            message = JSON.parse(message)
            let {event} = message
            try {
                eventTopic.emit(event, {Users, user, message})
            } catch (e) {
                console.log(e);
            }
        });
        
        ws.on('pong', function heartbeat() {  //心跳
            this.isAlive = true;
        });
        
        setInterval(() => {   //向客户端发送ping 确保客户端还‘活着’
            webSocket.clients.forEach(client => {
                if (client.isAlive === false) return ws.terminate();
                client.isAlive = false
                client.ping('ping');
            });
        }, 10000)
    });
}

 好文章,我在看❤️

你可能感兴趣的:(浅谈 Comet、SSE、WebSocket)