截至目前為止,我們所接觸到的項目內部都是基於HTTP協議實現通信的:http協議是無鏈接無狀態,客戶端發送請求,服務端返回響應,服務端不會自動朝客戶端發送消息。
有三種方式實現服務端主動向客戶端推送消息:
1.輪詢 2.長輪詢 3.websocket
輪詢
效率低、基本不用
讓瀏覽器定時朝后端發送請求(通過ajax向后端偷偷發送數據),比如每隔五秒鍾發一次請求,那么你的數據延遲就可能會高達五秒
不足之處
數據延遲
消耗資源過大
請求次數太多
長輪詢
兼容性好,一般大公司都會考慮使用它
# 隊列+ajax 服務端給每個客戶端建立隊列,讓瀏覽器通過ajax朝服務端要數據,去各自的隊列中獲取 如果沒有數據則會阻塞但是不會一直阻塞,比如阻塞你30秒,還沒有數據則返回,然后讓客戶端瀏覽器再次發送請求數據的請求。 讓瀏覽器內部偷偷的朝服務端獲取數據,客戶端第一次來的時候會給每一個客戶端創建一個獨有的隊列,之后客戶端請求數據都是從自己對應的隊列中索要,
由於隊列沒有數據的時候,get方法會阻塞一旦有數據又會立刻運行,所以我們利用timeout參數加異常捕獲的方式來做到基本零延遲
相對於輪詢 基本是沒有消息延遲的 請求次數降低了很多 # web版本的qq和微信基本上用的都是這么一個邏輯
基於ajax及隊列實現的長輪詢的功能(django簡易版的聊天室)
""" 1.首頁自定義用戶唯一表示,給每個用戶初始化一個隊列 2.發送按鈕綁定點擊事件 后端講數據放入每一個隊列中 3.書寫自動獲取數據的ajax代碼 循環調用 4.前端獲取數據DOM操作渲染頁面 """
urls.py
#基於ajax+隊列實現長輪詢,聊天頁面 url(r'^ab_bl/', views.ab_bl), #前端給后端發送信息 url(r'^send_msg/', views.send_msg), #展示前端的信息 url(r'^get_msg/', views.get_msg),
ab_bl.html
<body> <h1>{{ name }}聊天室</h1> <p> <input type="text" name="content" id="d2"> <button id="d1">發送</button> </p> <h1>聊天記錄</h1> <div id="content"></div> <script> //點擊發送觸發ajax $('#d1').click(function () { $.ajax({ url:'/send_msg/', //消息發送地址,后端 type:'post', data:{'content':$('#d2').val()}, //傳遞給后端的信息 success:function (data) { //后端返回給前端的消息 } }) }); function getMsg(){ $.ajax({ url:'/get_msg/', type:'get', data:{'name':'{{ name }}'}, // 只要當前登陸人的隊列中的數據 success:function (args) { // 針對返回的消息做相應的處理 if(args.status){ // 有消息則渲染頁面 講消息全局放到聊天紀錄里面 // 1 創建標簽 var pEle = $('<p>'); // 2 給標簽設置文本內容 pEle.text(args.msg); // 3 講創建好的標簽添加到聊天記錄div標簽內 $('#content').append(pEle) }else{ // 沒有消息 則繼續發送 } getMsg() // 循環請求數據 } }) } $(function () { getMsg() //頁面加載完畢自動執行 }) </script> </body>
views.py
from django.shortcuts import render,HttpResponse import queue from django.http import JsonResponse # 全局大字典 q_dict = {} # {'唯一表示':隊列,....} def ab_bl(request): #從路徑傳參name,用於做每個用戶的標識 name=request.GET.get('name') #給每一個客戶端創建一個隊列 q_dict[name]=queue.Queue() return render(request,'ab_bl.html',locals()) #前端給后端傳遞消息 def send_msg(request): if request.method == 'POST': #獲取用戶發送的消息:ajax中data傳遞的數據 content=request.POST.get('content') #將該消息傳遞給所有的隊列 for q in q_dict.values(): q.put(content) return HttpResponse('ok') #獲取隊列中數據 def get_msg(request): name = request.GET.get('name') # 拿到對應的隊列 q = q_dict.get(name) # 講隊列中可能有的數據取出並返回給前端瀏覽器 # 定義一個字典與ajax進行交互 back_dic = {'status': True, 'msg': ''} try: data = q.get(timeout=10) # 等10s 沒有則直接報錯 back_dic['msg'] = data except queue.Empty as e: back_dic['status'] = False return JsonResponse(back_dic)
websocket
目前主流的瀏覽器都是支持websocket
HTTP協議 網絡協議(不加密傳輸)
HTTPS協議 網絡協議(加密傳輸)
上面兩個協議都是短鏈接
websocket網絡協議 (加密傳輸)
瀏覽器和服務端創建鏈接之后 默認不再斷開
兩端都可以基於該鏈接收發消息
websocket的誕生能夠真正做到服務端發送消息而不再是被動的發送
websocket內部原理
分成兩大部分 1.握手環節:驗證服務端是否支持websocket協議 第一次訪問服務端的時候(基於http協議) 瀏覽器產生一個隨機字符串放在請求頭中給服務端發送一份,自己留一份 Sec-WebSocket-Key: ePW8kp1XqLNWbJxE/Q38SA== 服務端和客戶端都對隨機字符串做下面的操作 隨機字符串 + magic string拼接 然后再講拼接好的結果進行加密處理(sha1/base64)的到密文
服務端將產生的密文通過響應頭再次發送給客戶端瀏覽器 瀏覽器自動比對雙方產生的密文是否一致,如果一致說明服務端支持websocket 如果不一致會報錯 假設比對上了 建立websocket鏈接 基於該鏈接收發消息 2.收發數據 密文傳輸 >>> 必然要涉及解密(全球統一)的過程 基於網絡傳輸的數據都是二進制格式 對應到我們python中就是bytes類型 數據解密過程 1.對收到的消息,先讀取數據的第2個字節的后7位(payload)字節 根據7位數據的大小來指定不同的解密流程 =127:再往后讀取8個字節 =126:再往后讀取2個字節 <=125:不再往后讀取 除去前面讀取的數據之外 再往后讀4個字節(masking-key) 拿着它去解析后面的真實數據(依據一個計算公式)
代碼驗證
后端代碼無需掌握,前端的就行
<!--前端只需要寫一行代碼就可以了--> <script> var ws = new WebSocket('ws://127.0.0.1:22/') </script> <!--通過ws對象點send方法即可實現websocket的數據交互-->