01: websocket

1.1 Websocket原理

  1、什么是webSocket?

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

      2. 客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

      3. 浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

      远古时期解决方案就是轮训:客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动(浪费流量和资源)

  2、webSocket应用场景?

      1. 聊天软件:最著名的就是微信,QQ,这一类社交聊天的app

      2. 弹幕:各种直播的弹幕窗口

      3. 在线教育:可以视频聊天、即时聊天以及其与别人合作一起在网上讨论问题…

  3、图解http与webSocket比较

      1. 浏览器通过 JavaScript 向服务端发出建立 WebSocket 连接的请求

      2. 在 WebSocket 连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。

      3. 因为WebSocket 连接本质上是 TCP 连接,不需要每次传输都带上重复的头部数据

      

  4、websocket原理

      1. websocket首先借助http协议(通过在http头部设置属性,请求和服务器进行协议升级,升级协议为websocket的应用层协议)

      2. 建立好和服务器之间的数据流,数据流之间底层还是依靠TCP协议;

      3. websocket会接着使用这条建立好的数据流和服务器之间保持通信;

      4. 由于复杂的网络环境,数据流可能会断开,在实际使用过程中,我们在onFailure或者onClosing回调方法中,实现重连

 1.2 webSocket使用

  1、webSocket使用说明

      1. 如果你想为一个单独的视图处理一个websocklet连接可以使用accept_websocket装饰器,它会将标准的HTTP请求路由到视图中。

      2. 使用require_websocke装饰器只允许使用WebSocket连接,会拒绝正常的HTTP请求。

      3. 在设置中添加设置MIDDLEWARE_CLASSES=dwebsocket.middleware.WebSocketMiddleware这样会拒绝单独的视图实用websocket,必须加上accept_websocket 装饰器。

      4. 设置WEBSOCKET_ACCEPT_ALL=True可以允许每一个单独的视图实用websockets

  2、Django的Websocket

    1)dwebsocket介绍

        1. dwebsocket 是一个在 django 用来实现 websocket 服务端的三方模块,使用上手非常简单

        2. 安装方式如下:pip install dwebsocket

        3. git 地址:https://github.com/duanhongyi/dwebsocket

    2)dwebsocket方法

首先是两个基本的装饰器,用来限定过滤 websocket 的连接
dwebsocket.accept_websocket   # 允许 http 与 websocket 连接
dwebsocket.require_websocke   # 只允许 websocket 连接
1.request.is_websocket()如果是个websocket请求返回True,如果是个普通的http请求返回False,可以用这个方法区分它们。
2.request.websocket()websocket请求建立之后,有一个websocket属性,如果request.is_websocket()是False,这个属性将是None。
3.WebSocket.wait()返回一个客户端发送的信息,在客户端关闭连接之前他不会返回任何值,这种情况下,方法将返回None
4.WebSocket.read()如果没有从客户端接收到新的消息,read方法会返回一个新的消息,如果没有,就不返回。这是一个替代wait的非阻塞方法
5.WebSocket.count_messages()返回消息队列数量
6.WebSocket.has_messages() 如果有新消息返回True,否则返回False
7.WebSocket.send(message) 向客户端发送消息
8.WebSocket.__iter__()websocket迭代器

  3、HTMLWebsocket 

    1)响应事件

# 1. 创建一个WebSocket连接
var ws = new WebSocket("ws://127.0.0.1:8001/echo")

# 2. ws.onopen方法(当 ws 连接建立时触发)
ws.onopen = function () {
    console.log('WebSocket open');   //成功连接上Websocket
};

# 3. 当 ws 连接接收到数据时触发
ws.onmessage = function (e) {
    console.log('message: ' + e.data);  //打印出服务端返回过来的数据
};

# 4. 当 ws 连接发生通信错误时触发
ws.onerror = function () {
    console.log('连接出错');
};

# 5. 当连接关闭时触发
ws.onclose = function(){
    console.log('连接关闭')
}

    2)方法

ws.send(str)  // 通过ws连接发送数据
ws.close()    // 关闭ws连接

  4、django+webSocket通信

from django.conf.urls import url
from django.contrib import admin
from app01 import views as v

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', v.index),
    url(r'^echo$', v.echo),
]
urls.py
from django.shortcuts import render
from dwebsocket.decorators import accept_websocket,require_websocket
from django.http import HttpResponse


def index(request):
    return render(request, 'index.html')

from dwebsocket.backends.default.websocket import DefaultWebSocket  # request.websocket就是DefaultWebSocket对象

tmp = []
# 只有加了这个装饰器,这个视图函数才能处理websocket请求
@accept_websocket
def echo(request):
    if not request.is_websocket():  #判断是不是websocket连接
        try:  #如果是普通的http方法
            message = request.GET['message']
            return HttpResponse(message)
        except:
            return render(request,'index.html')
    else:
        '''1.实现消息推送'''
        tmp.append(request.websocket)  # 把所有连接的websocket连接都加入列表中
        #  request.websocket = <dwebsocket.backends.default.websocket.DefaultWebSocket object at 0x00000272E69A4320>
        # failed:Invalid frame header:你的视图没有阻塞,请求过一次后服务器端就关闭连接了
        # 所以使用for循环 request.websocket 对象就会调用 __iter__()方法,利用迭代器进行阻塞
        for message in request.websocket:
            for ws in tmp:
                ws.send(message)

        '''2.实现聊天室思路'''
        # d = {}                                # 使用了一个dict来保存数据,
        # d['zhangsan'] = request.websocket     # key值是用户身份,value值是dict类型的{username:websocket}。
        # d['zhangsan'].send(message)           # 发送消息到客户端
        # d['lisi'].send(message)   ==>  request.websocket.send(message)

        # 这只是个思路,如果正式使用的话,肯定会对group封装,也不会只保存在内存中,需要保存到redis中去
        # 并且对每个websocket对象设置有效期,过期清除,避免长期挂起状态消耗系统资源等
views.py
<!DOCTYPE html>
<html>
 <head>
  <title>django-websocket</title>
 </head>
 <body>
  <input type="text" id="message" value="Hello, World!" />
  <button type="button" id="connect_websocket">连接 websocket</button>
  <button type="button" id="send_message">发送 message</button>
  <button type="button" id="close_websocket">关闭 websocket</button>
  <h1>Received Messages</h1>

  <div id="messagecontainer"></div>

  <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
  <script type="text/javascript">
    $(function () {
        $('#connect_websocket').click(function () {
            if (window.s) {
                window.s.close()
            }
            /*创建socket连接*/
            var ws = new WebSocket("ws://" + window.location.host + "/echo");
            console.log(ws, 88888888888888888)
            ws.onopen = function () {
                console.log('WebSocket open');//成功连接上Websocket
            };
            ws.onmessage = function (e) {
                console.log('message: ' + e.data);//打印出服务端返回过来的数据
                $('#messagecontainer').prepend('<p>' + e.data + '</p>');
            };
            // Call onopen directly if socket is already open
            if (ws.readyState == WebSocket.OPEN) ws.onopen();
            window.s = ws;
        });
        $('#send_message').click(function () {
            //如果未连接到websocket
            if (!window.s) {
                alert("websocket未连接.");
            } else {
                window.s.send($('#message').val());//通过websocket发送数据
            }
        });
        $('#close_websocket').click(function () {
            if (window.s) {
                window.s.close();//关闭websocket
                console.log('websocket已关闭');
            }
        });

    });
    </script>
 </body>
</html>
index.html
<template>
  <div>
    <button @click="send">发消息</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        path:"ws://127.0.0.1:8000/echo?username=zhangsan&token=xxxx",
        socket:""
      }
    },
    mounted () {
      // 初始化
      this.init()
    },
    methods: {
      init: function () {
        if(typeof(WebSocket) === "undefined"){
          alert("您的浏览器不支持socket")
        }else{
          // 实例化socket
          this.socket = new WebSocket(this.path)
          // 监听socket连接
          this.socket.onopen = this.open
          // 监听socket错误信息
          this.socket.onerror = this.error
          // 监听socket消息
          this.socket.onmessage = this.getMessage
        }
      },
      open: function () {
        console.log("socket连接成功")
      },
      error: function () {
        console.log("连接错误")
      },
      getMessage: function (msg) {
        console.log(msg.data)         // 打印后台返回的数据
      },
      send: function () {
        var params = 'hahahahhahaha';
        this.socket.send(params)      // 发送给后台的数据
      },
      close: function () {
        console.log("socket已经关闭")
      }
    },
    destroyed () {
      // 销毁监听
      this.socket.onclose = this.close
    }
  }
</script>

<style>

</style>
WS.vue 在vue中使用代码

 

   

  

1.3 websocket心跳包机制

     参考:https://www.cnblogs.com/tugenhua0707/p/8648044.html

   1、心跳包的意义

      1. 在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。

      2. 这样会有:服务器会继续向客户端发送多余的链接,并且这些数据还会丢失。

      3. 所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态。

      4. 因此就有了websocket的心跳了,还有心跳,说明还活着,没有心跳说明已经挂掉了。

  2、实现心跳检测的思路

      1. 通过setInterval定时任务每个3秒钟调用一次reconnect函数

      2. reconnect会通过socket.readyState来判断这个websocket连接是否正常

      3. 如果不正常就会触发定时连接,每4s钟重试一次,直到连接成功

      4. 如果是网络断开的情况下,在指定的时间内服务器端并没有返回心跳响应消息,因此服务器端断开了。

      5. 服务断开我们使用ws.close关闭连接,在一段时间后,可以通过 onclose事件监听到。

  3、使用vue+django实现心跳检查

    1)django

from django.conf.urls import url
from django.contrib import admin
from app01 import views as v

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', v.index),
    url(r'^echo$', v.echo),
]
urls.py
from django.shortcuts import render
from dwebsocket.decorators import accept_websocket,require_websocket
from django.http import HttpResponse
from collections import defaultdict

def index(request):
    return render(request, 'index2.html')

allconn = defaultdict(list)

@accept_websocket
def echo(request):
    username = request.GET.get('username')  # 模拟用户登录
    token = request.GET.get('token')        # 模拟用户身份验证
    if not request.is_websocket():  #判断是不是websocket连接
        try:  #如果是普通的http方法
            message = request.GET['message']
            return HttpResponse(message)
        except:
            return render(request,'index2.html')
    else:
        # 以用户名为 字典key  ws连接对象为 value放入字典,如果想要将信息发给指定人只需要 allconn[username].send()即可
        allconn[str(username)] = request.websocket  # {"zhangsan":"zhangsan websocket连接", "lisi":"lisi websocket连接"}
        for message in request.websocket:
            request.websocket.send(message) #发送消息到客户端
app01/views.py
<!DOCTYPE html>
<html>
 <head>
  <title>django-websocket</title>
 </head>
 <body>
  <input type="text" id="message" value="Hello, World!" />
  <button type="button" id="connect_websocket">连接 websocket</button>
  <button type="button" id="send_message">发送 message</button>
  <button type="button" id="close_websocket">关闭 websocket</button>
  <h1>Received Messages</h1>

  <div id="messagecontainer"></div>

  <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
  <script type="text/javascript">
    $(function () {
        $('#connect_websocket').click(function () {
            if (window.s) {
                window.s.close()
            }
            /*创建socket连接*/
            var ws = new WebSocket("ws://" + window.location.host + "/echo");
            console.log(ws, 88888888888888888)
            ws.onopen = function () {
                console.log('WebSocket open');//成功连接上Websocket
            };
            ws.onmessage = function (e) {
                console.log('message: ' + e.data);//打印出服务端返回过来的数据
                $('#messagecontainer').prepend('<p>' + e.data + '</p>');
            };
            // Call onopen directly if socket is already open
            if (ws.readyState == WebSocket.OPEN) ws.onopen();
            window.s = ws;
        });
        $('#send_message').click(function () {
            //如果未连接到websocket
            if (!window.s) {
                alert("websocket未连接.");
            } else {
                window.s.send($('#message').val());//通过websocket发送数据
            }
        });
        $('#close_websocket').click(function () {
            if (window.s) {
                window.s.close();//关闭websocket
                console.log('websocket已关闭');
            }
        });

    });
    </script>
 </body>
</html>
index.html

    2)vue

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import WS from '@/components/WS'

Vue.use(Router)

export default new Router({
  routes: [
    { path: '/', name: 'HelloWorld', component: HelloWorld },
    { path: '/ws', name: 'WS', component: WS },
  ]
})
src/router/index.js
<template>
  <div>
    <input v-model="msg">
    <button @click="send">发消息</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        msg:"",
        path:"ws://127.0.0.1:8000/echo?username=zhangsan&token=xxxx",  // 连接后台websocket地址
        socket:"",             // websocket实例
        lockReconnect:false,  // 避免重复连接websocket的标识
        tt:'',                 // 定时任务初始化变量
      }
    },
    mounted () {
      // 初始化
      this.init()
      this.heartCheck()
    },
    methods: {
      init: function () {
        console.log('初始化websocket')
        if(typeof(WebSocket) === "undefined"){
          alert("您的浏览器不支持socket")
        }else{
          // 实例化socket
          this.socket = new WebSocket(this.path)
          // 监听socket连接
          this.socket.onopen = this.open
          // 监听socket错误信息
          this.socket.onerror = this.error
          // 监听socket消息
          this.socket.onmessage = this.getMessage

          this.socket.onclose = this.close
        }
      },
      open: function () {
        console.log("socket连接成功")
      },
      error: function () {
        console.log("连接错误")
        this.reconnect()
      },
      getMessage: function (msg) {
        console.log(msg.data)         // 打印后台返回的数据
        // this.heartCheck()
      },
      send: function () {
        var params = this.msg
        this.socket.send(params)      // 发送给后台的数据
      },
      close: function () {
        console.log("socket已经关闭")
        this.reconnect()
      },
      // 重新连接websocket
      reconnect: function () {
        var _this = this
        if(_this.socket.readyState===1) {  // 如果状态等于1代表 websocket连接正常
          return;
        };
        _this.lockReconnect = true;
        //没连接上会一直重连,设置延迟避免请求过多
        _this.tt && clearTimeout(_this.tt);
        _this.tt = setTimeout(function () {
          _this.init()
          _this.lockReconnect = false;
        }, 4000);
      },
      // 对websocket监控检查
      heartCheck: function () {
        var _this = this
        _this.tt = setInterval(function () {
          _this.reconnect();
        }, 4000);
      }
    },
    destroyed () {
      // 销毁监听
      this.close()
      clearTimeout(_this.tt)  // 关闭定时
    }
  }
</script>

<style>

</style>
src/components/WS.vue

 

posted @ 2020-01-28 19:25  不做大哥好多年  阅读(738)  评论(0编辑  收藏  举报