vlambda博客
学习文章列表

数据实时可视化——实时交互(websocket、SSE)

在许多场景下,数据分析需要做实时可视化,以满足实时监测的需要。实时可视化的前端可视化库有不少选择,如echarts,但不管前端用什么可视化库,前端与后端通信总是一个需要处理的问题,一般的处理方式有轮询,长连接,websocket,sse等。这几种处理方式有各自不同特点。

轮询

与后端做实时交互首先想到的方案是轮询。轮询是最直接的,就是设定一个间隔时间,每隔一段时间向后端服务器发送请求,刷新前端数据。轮询的缺点是很明显的,因为不知道服务器什么时候有新数据,需要不断发请求,很多请求都是无用请求,而且前端在每次请求都在重新刷新渲染。这样不光会浪费后端资源,前端的不断无用刷新可能影响用户体验,另外间隔时间的设定也是一个问题。但是,轮询有一个优势就是适应性好,iframe或者ajax一般浏览器都支持。

var realTime = {
  url'realTimeData',
  frushfunction(){
    $.get(this.url, function(data, status){
      if(status===200){
        console.log(data)
      }else{
        console.log("Have an err")
      }
    })
  }
}
setInterval(realtime.frush(),1000);

websocket

websocket可能是实时交互最受欢迎的方案。websocket作为一种全双工、双向持久链接通信方案,它有很多优点,相比于轮询,减少了客户端与服务器之间的数据发送量,这样服务器与客户端都可避免不必要的资源消耗。websocket也有不好的地方,首先后端需要支持websocket的框架,前端上有部分浏览器还不支持。

一个心跳检测的websocket例子

function createConnect(url){
  try{
    if('WebSocket' in window){
      var ws = new WebSocket(url)
    }else if('MozWebSocket' in window){
      var ws = new MozWebSocket(url)
    }else{
      console.log('浏览器不支持websock')
    }
    initWs(url);
  }catch(e){
    reconnect(url)
  }
}

function initWs(url){
  ws.onopen = function(){
    console.log('ws连接成功')
  };
  ws.onclose = function(){
    console.log('ws连接关闭');
    reconnect(url)
  };
  ws.onerror = function(){
    console.log('ws连接出错')
    reconnect(url)
  };
  ws.onmessage = function(){
    heartCheck.init().start();
    if(event.data !== 'pong'){
      console.log('接收到信息:'+event.data)
    }
  }
  
}

function reconnect(url){
  if(lockReconnect) return;
  lockReconnect = true;
  setTimeout(function(){
    creatWsConnect(url);
    lockReconnect=false;
  },2000);
}

var heartCheck = {
  timeout60*1000,
  timeoutObj:null,
  serverTimeoutObjnull,
  initfunction(){
    clearTimeout(this.timeoutObj);
    clearTimeout(this.serverTimeoutObj);
    return this;
  },
  startfunction(){
    var that = this;
    this.timeoutObj = setTimeout(function(){
      ws.send('ping');
      that.serverTimeoutObj = setTimeout(function(){
        ws.close();
      },that.timeout)
    },this.timeout)
  }
}
var lockReconnect = false;
var ws = null;
var wsUrl = 'ws://';
creatWsConnect(wsUrl);

server端--Python

import asyncio, random
import websockets
async def index(websocket, path):
  text = await websocket.recv()
  if(text == 'ping'):
    await websocket.send('pong')
   else:
    print(text)
    await websocket.send("收到")
    
start_server = websockets.serve(index, 'localhost'8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

sse

sse(server-sentevents, 服务器发送事件)是服务器到客户端的单向连接。sse的优势是,一般浏览器都支持,相对于websocket实现简单,另外特别适合实时可视化的需求,服务器向客户端的单向推送。

用flask-sse的一个例子,是基于Redis发布/订阅,需要安装Redis。

server端

from flask import Flask, render_template
from flask_sse import sse

app = Flask(__name__)
app.config['REDIS_URL'] = "redis://localhost"
app.register_blueprint(sse, url_prefix='/stream')

@app.route('/')
def index():
  return render_template('index.html')

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Demo</title>
</head>
<body>
    <h1>flask-sse</h1>
    <script type="text/javascript">
        var source = new EventSource("{{ url_for('sse.stream') }}")
        source.onmessage=function(event){
            var data = JSON.parse(event.data);
            console.log("收到信息"+data.message);
        };  
    
</script>
</body>
</html>

Redis

# flask-sse默认发布订阅频道是sse
# redis-cli
publish sse "{\"data\":{\"message\":\"测试\"}}"

封面来源:Pexels 上的 Kelvin Valerio 拍摄的图片