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、HTML的Websocket
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), ]

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對象設置有效期,過期清除,避免長期掛起狀態消耗系統資源等

<!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>

<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>
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), ]

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) #發送消息到客戶端

<!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>
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 },
]
})

<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>