一 、什么是Websocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

现在,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯

websocket作用及意义

Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道http协议是一种单向的网络协议,在建立连接后,它只允许Browser/UA(UserAgent)向WebServer发出请求资源后,WebServer才能返回相应的数据。而WebServer不能主动的推送数据给Browser/UA,当初这么设计http协议也是有原因的,假设WebServer能主动的推送数据给Browser/UA,那Browser/UA就太容易受到攻击,一些广告商也会主动的把一些广告信息在不经意间强行的传输给客户端,这不能不说是一个灾难。那么单向的http协议给现在的网站或Web应用程序开发带来了哪些问题呢?

让我们来看一个案例,现在假设我们想开发一个基于Web的应用程序去获取当前Web服务器的实时数据,例如股票的实时行情,火车票的剩余票数等等,这就需要Browser/UA与WebServer端之间反复的进行http通信,Browser不断的发送Get请求,去获取当前的实时数据。下面介绍几种常见的方式:

二、什么是ECharts

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

特性

  1. 丰富的可视化类型
  2. 多种数据格式无需转换直接使用
  3. 千万数据的前端展现
  4. 移动端优化
  5. 多渲染方案,跨平台使用!
  6. 深度的交互式数据探索
  7. 多维数据的支持以及丰富的视觉编码手段
  8. 动态数据
  9. 绚丽的特效
  10. 通过 GL 实现更多更强大绚丽的三维可视化
  11. 无障碍访问(4.0+)

三、Django实现Websocket

django实现websocket大致上有两种方式,一种channels,一种是dwebsocket。channels依赖于redis,twisted等,相比之下使用dwebsocket要更为方便一些

#dwebsocket有两种装饰器:require_websocket和accept_websocekt,使用require_websocket装饰器会导致视图函数无法接收导致正常的http请求,一般情况使用accept_websocket方式就可以了,
# 
# dwebsocket的一些内置方法:
# 
# request.is_websocket():判断请求是否是websocket方式,是返回true,否则返回false
# request.websocket: 当请求为websocket的时候,会在request中增加一个websocket属性,
# WebSocket.wait() 返回客户端发送的一条消息,没有收到消息则会导致阻塞
# WebSocket.read() 和wait一样可以接受返回的消息,只是这种是非阻塞的,没有消息返回None
# WebSocket.count_messages()返回消息的数量
# WebSocket.has_messages()返回是否有新的消息过来
# WebSocket.send(message)像客户端发送消息,message为byte类型

安装

pip install dwebsocket

dwebsocket配置

INSTALLED_APPS = [
    'dwebsocket',
]
MIDDLEWARE = [
    'dwebsocket.middleware.WebSocketMiddleware',
    # 为所有的URL提供websocket,如果只是单独的视图需要可以不选
]

使用

案例 根据自己的情况进行修改

from dwebsocket.decorators import accept_websocket

@accept_websocket
def test_websocket(request):
    if request.is_websocket():
        # 定时推送 无限循环
        while 1:
            time.sleep(5)  ## 每隔5秒发送一次
            request.websocket.send(json.dumps({"msg":"发送的内容"}))

后端接口

  1. 首先获取数据 存储到mongodb

    data_test.py

    from pymongo import MongoClient
    from datetime import datetime
    
    host = 'localhost'
    
    # client = MongoClient(host, 27017)  # 建立客户端对象
    # db = client.mydb  # 连接mydb数据库,没有则自动创建
    # myset = db.testset  # 使用test_set集合,没有则自动创建
    client = MongoClient(host='127.0.0.1', port=27017)
    db = client['test3']
    p = db.persons
    
    # 在PyMongo 3.x版本后,官方推荐使用insert_one(),该方法返回的不再是单纯的_id值,我们需要执行result.inserted_id查看 _id 值
    
    ress = p.find()  # 查询集合中age是20的数据
    # res = p.find({'age':{'$gt':20}})  # 查询集合中age大于20的数据
    
    import requests
    
    res = requests.get(
        'https://data.gtimg.cn/flashdata/hushen/latest/daily/sz000002.js?maxage=43201&visitDstTime=1')  # 向get中传入一个网址
    # type(res)  # 响应对象lass 'requests.models.Response'>
    # print(res.status_code)  # 响应码
    a = []
    b = {}
    s = 0
    
    
    # print(res.text.split("\n"))
    def add_stock():
        for i in res.text.split("\n")[2:-1]:
            d = (i[:-3].split(" "))
    
            b["_id"] = d[0]
            b["p1"] = d[1]
            b["p2"] = d[2]
            b["p3"] = d[3]
            b["p4"] = d[4]
            b["day"] = d[5]
            p.insert_one(b)
  2. 在视图中views.py

    # 今日股市
    @accept_websocket
    def test_websocket(request):
        if request.is_websocket():
            while 1:
                data = []
                for i in p.find():
                    data.append(i)
                # [{'_id': '200729', 'p1': '26.71', 'p2': '27.11', 'p3': '27.14', 'p4': '26.51', 'day': '926894'}]
                request.websocket.send(json.dumps(data))
                p.remove()
                add_stock()
                inform_user()
                time.sleep(10)  # 向前端发送时间
  3. 路由urls.py

    from django.urls import path, include
    from .views import *
    
    
    urlpatterns = [
        path('websocketlink/', test_websocket),  # websocket常连接实时跟新
    ]

四、前端Vue

事例

因为我们是实时更新 所以需要写在钩子函数中 关于vue生命周期点击查看

mounted: function () {

  var ed2020 = "2020";

  var sign = md5('price=500&goodid=3,1' + ed2020);

  console.log(sign);


  var _this = this;


  //判断浏览器是否支持websocket
  if ("WebSocket" in window) {

    console.log("支持");
    //生成websocket链接
    var ws = new WebSocket("ws://localhost:8000/user/send_websocket/");

    //发送链接请求
    ws.onopen = function () {

      ws.send("test");
    }
    //发送消息
    ws.onmessage = function (evt) {

      //将获取信息打印
      var received_msg = evt.data;

      // alert(received_msg);
    }
    //捕获断开链接
    ws.onclose = function () {

      console.log("链接已经关闭");
    }
  }
},

statistics.vue

<template>
  <div>
    <h1 align="center">今日股票</h1>
    <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
    <div id="main" :style="{width: '2000px',height:'600px'}"></div>
  </div>
</template>

<script>
import echarts from 'echarts'
import {getStatistics} from "../http/apis";
import md5 from "js-md5";

export default {
  name: "statistics",
  data() {
    return {}
  },
  methods: {
    main() {
      var data
      var myChart = echarts.init(document.getElementById('main'));
      let id_list = []
      let p_list = []

      getStatistics().then(res => {
        data = res.data
        for (var i in data) {
          id_list.push(data[i]._id)
          p_list.push(data[i].p1)
          var option = {
            title: {
              text: '今日股市'
            },
            tooltip: {},
            legend: {},
            xAxis: {
              // data: id_list
              data: id_list
            },
            yAxis: {},
            series: [{
              name: '每股成交额',
              type: 'bar',
              data: p_list
              // data: pr_list
            }]
          };
          myChart.setOption(option);
        }
      })
      console.log(this.id_list)
    }
  },
  mounted() {
    // this.main()
    var ed2020 = "2020";

    var sign = md5('price=500&goodid=3,1' + ed2020);

    console.log(sign);


    var _this = this;


    //判断浏览器是否支持websocket
    if ("WebSocket" in window) {

      console.log("支持");
      //生成websocket链接
      var ws = new WebSocket("ws://localhost:8000/user/websocketlink/");
      //发送链接请求
      ws.onopen = function () {
        ws.send("test");
      }
      //发送消息
      ws.onmessage = function (evt) {
        //将获取信息打印
        var received_msg = evt.data;

        var data
        var myChart = echarts.init(document.getElementById('main'));
        let id_list = []
        let p1_list = []
        let p2_list = []
        let p3_list = []
        let p4_list = []

        data = JSON.parse(received_msg)
        console.log(data)
        for (var i in data) {
          id_list.push(data[i]._id)
          p1_list.push(data[i].p1)
          p2_list.push(data[i].p2)
          p3_list.push(data[i].p3)
          p4_list.push(data[i].p4)

          var option = {
            title: {
              text: '今日股市'
            },
            tooltip: {},
            legend: {},
            xAxis: {
              // data: id_list
              data: id_list
            },
            yAxis: {},
            series: [
              {
                name: '每股成交额',
                type: 'bar',
                data: p1_list
                // data: pr_list
              },
              {
                name: '每股成交额',
                type: 'bar',
                data: p2_list
                // data: pr_list
              },
              {
                name: '每股成交额',
                type: 'bar',
                data: p3_list
                // data: pr_list
              },
              {
                name: '每股成交额',
                type: 'bar',
                data: p4_list
                // data: pr_list
              },
            ],

          };
          myChart.setOption(option);
        }
      }
      //捕获断开链接
      ws.onclose = function () {
        console.log("链接已经关闭");
      }
    }
  },
}
</script>

<style scoped>

</style>

最终效果

ok搞定收工