關於WebSocket:
WebSocket 協議在2008年誕生,2011年成為國際標准。現在所有瀏覽器都已經支持了。
WebSocket 的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話。
1. webSocket是一種在單個TCP連接上進行全雙工通信的協議
2. 客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。
3. 瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸
遠古時期解決方案就是輪訓:客戶端以設定的時間間隔周期性地向服務端發送請求,頻繁地查詢是否有新的數據改動(浪費流量和資源)
WebSocket 的其他特點:
-
- 建立在 TCP 協議之上,服務器端的實現比較容易。
- 與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
- 數據格式比較輕量,性能開銷小,通信高效。
- 可以發送文本,也可以發送二進制數據。
- 沒有同源限制,客戶端可以與任意服務器通信。
- 協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。
WebSocket使用場景:
1. 聊天軟件:微信,QQ,這一類社交聊天的app
2. 彈幕:各種直播的彈幕窗口
3. 在線教育:可以視頻聊天、即時聊天以及其與別人合作一起在網上討論問題…
WebSocket與HTTP:
相對於 HTTP 這種非持久的協議來說,WebSocket 是一個持久化的協議。
HTTP 的生命周期通過 Request 來界定,也就是一個 Request 一個 Response ,那么在 HTTP1.0 中,這次 HTTP 請求就結束了。
在 HTTP1.1 中進行了改進,有一個 keep-alive,在一個 HTTP 連接中,可以發送多個 Request,接收多個 Response。
但是請記住 Request = Response, 在 HTTP 中永遠是這樣,也就是說一個 Request 只能有一個 Response。而且這個 Response 也是被動的,不能主動發起
基於HTTP
短連接如何保障數據的即時性
HTTP
的特性就是無狀態的短連接,當地小有名氣的健忘鬼 即一次請求一次響應斷開連接失憶 ,這樣服務端就無法主動的去尋找客戶端給客戶端主動推送消息
1.輪詢
即: 客戶端不斷向服務器發起請求索取消息
優點: 基本保障消息即時性
缺點: 大量的請求導致客戶端和服務端的壓力倍增
客戶端:有沒有新消息呀?(Request) 服務端:emmm 沒有(Response) 客戶端:嘿 現在有沒有新信息嘞?(Request) 服務端:沒有。。。(Response) 客戶端:啦啦啦,有沒有新信息?(Request) 服務端:沒有啊 你有點煩哎(Response) 客戶端:那現在有沒有新消息?(Request) 服務端:好啦好啦,有啦給你。(Response) 客戶端:有沒有新消息呀?(Request) 服務端:沒有哦。。。(Response)
2.長輪詢
即: 客戶端向服務器發起請求,在HTTP
最大超時時間內不斷開請求獲取消息,超時后重新發起請求
優點: 基本保障消息即時性
缺點: 長期占用客戶端獨立線程,長期占用服務端獨立線程(消耗大量線程),服務器壓力倍增
客戶端:喂 有新的信息嗎(Request) 服務端:emmm 沒有 等有了就給你!(Response) 客戶端:這樣啊 那我很閑 我等着(Request)
從上面可以看出這兩種方式,都是在不斷地建立HTTP連接,然后等待服務端處理,體現HTTP協議的被動性。這樣非常消耗資源
輪詢 需要服務器有很快的處理速度和資源。長輪詢 需要有很高的並發。
1.socketio
長連接協議
優點:消息即時,兼容性強
缺點:接入復雜度高,為保障兼容性冗余依賴過多較重
2.websocket
長連接協議
優點:消息即時,輕量級,靈活適應多場景,機制更加成熟
缺點:相比socket
兼容性較差
客戶端:喂 有新的信息嗎 服務端:emmm 沒有 等有了就給你! 客戶端:那麻煩你了! 服務端:沒事哦 服務端:來啦來啦 有新的消息 服務端:神奇寶貝不神奇了是什么? 客戶端:收到 寶貝 客戶端:嘻嘻嘻
經過一次 HTTP 請求,就可以源源不斷信息傳送!
總體來說,Socketio
緊緊只是為了解決通訊而存在的,而Websocket
是為了解決更多更復雜的場景通訊而存在的
這里推薦Websocket
的原因是因為,我們的Django
框架甚至是Flask
框架,都有成熟的第三方庫
而且Tornado
框架集成
Django實現WebSocket:
大概流程:
-
下載
-
注冊到setting.py里的app
-
在setting.py同級的目錄下注冊channels使用的路由----->routing.py
-
將routing.py注冊到setting.py
-
把urls.py的路由注冊到routing.py里
-
編寫wsserver.py來處理websocket請求
使用Django
來實現Websocket
服務的方法很多在這里我們推薦技術最新的Channels
庫來實現
1.安裝DjangoChannels
Channels
安裝如果你是Windows
操作系統的話,那么必要條件就是Python3.7
pip install channels
2.配置DjangoChannels
1.創建項目 ChannelsReady
django-admin startprobject ChannelsReady
2.在項目的settings.py
同級目錄中,新建文件routing.py
1 # routing.py 2 from channels.routing import ProtocolTypeRouter 3 4 application = ProtocolTypeRouter({ 5 # 暫時為空 6 })
3.在項目配置文件settings.py
中寫入
1 INSTALLED_APPS = [ 2 'channels' 3 ] 4 5 ASGI_APPLICATION = "ChannelsReady.routing.application"
出現以下情況:
Django version 3.0.2, using settings 'ChannelsReady.settings' Starting ASGI/Channels version 2.4.0 development server at http://0.0.0.0:8000/ Quit the server with CTRL-BREAK.
1.創建一個新的應用chats
python manage.py startapp chats
2.在settings.py
中注冊chats
1 INSTALLED_APPS = [ 2 'chats', 3 'channels' 4 ]
3.在chats
應用中新建文件
1 from channels.generic.websocket import WebsocketConsumer 2 # 這里除了 WebsocketConsumer 之外還有 3 # JsonWebsocketConsumer 4 # AsyncWebsocketConsumer 5 # AsyncJsonWebsocketConsumer 6 # WebsocketConsumer 與 JsonWebsocketConsumer 就是多了一個可以自動處理JSON的方法 7 # AsyncWebsocketConsumer 與 AsyncJsonWebsocketConsumer 也是多了一個JSON的方法 8 # AsyncWebsocketConsumer 與 WebsocketConsumer 才是重點 9 # 看名稱似乎理解並不難 Async 無非就是異步帶有 async / await 10 # 是的理解並沒有錯,但對與我們來說他們唯一不一樣的地方,可能就是名字的長短了,用法是一模一樣的 11 # 最誇張的是,基類是同一個,而且這個基類的方法也是Async異步的 12 13 class ChatService(WebsocketConsumer): 14 # 當Websocket創建連接時 15 def connect(self): 16 pass 17 18 # 當Websocket接收到消息時 19 def receive(self, text_data=None, bytes_data=None): 20 pass 21 22 # 當Websocket發生斷開連接時 23 def disconnect(self, code): 24 pass
1.在chats
應用中,新建urls.py
1 from django.urls import path 2 from chats.chatService import ChatService 3 websocket_url = [ 4 path("ws/",ChatService) 5 ]
2.回到項目routing.py
文件中增加ASGI
非HTTP
請求處理
1 from channels.routing import ProtocolTypeRouter,URLRouter 2 from chats.urls import websocket_url 3 4 application = ProtocolTypeRouter({ 5 "websocket":URLRouter( 6 websocket_url 7 ) 8 })
1.基於vue的websocket客戶端
1 <template> 2 <div> 3 <input type="text" v-model="message"> 4 <p><input type="button" @click="send" value="發送"></p> 5 <p><input type="button" @click="close_socket" value="關閉"></p> 6 </div> 7 </template> 8 9 10 <script> 11 export default { 12 name:'websocket1', 13 data() { 14 return { 15 message:'', 16 testsocket:'' 17 } 18 }, 19 methods:{ 20 send(){ 21 22 // send 發送信息 23 // close 關閉連接 24 25 this.testsocket.send(this.message) 26 this.testsocket.onmessage = (res) => { 27 console.log("WS的返回結果",res.data); 28 } 29 30 }, 31 close_socket(){ 32 this.testsocket.close() 33 } 34 35 }, 36 mounted(){ 37 this.testsocket = new WebSocket("ws://127.0.0.1:8000/ws/") 38 39 40 // onopen 定義打開時的函數 41 // onclose 定義關閉時的函數 42 // onmessage 定義接收數據時候的函數 43 // this.testsocket.onopen = function(){ 44 // console.log("開始連接socket") 45 // }, 46 // this.testsocket.onclose = function(){ 47 // console.log("socket連接已經關閉") 48 // } 49 } 50 } 51 </script> 基於vue websocket客戶端實現
------------------------>
客戶端保持不變,同時打開多個客戶端
服務端存儲每個鏈接的對象
1 socket_list = [] 2 3 class ChatService(WebsocketConsumer): 4 # 當Websocket創建連接時 5 def connect(self): 6 self.accept() # 保持狀態 7 socket_list.append(self) 8 9 # 當Websocket接收到消息時 10 def receive(self, text_data=None, bytes_data=None): 11 print(text_data) # 打印收到的數據 12 for ws in socket_list: # 遍歷所有的WebsocketConsumer對象 13 ws.send(text_data) # 對每一個WebsocketConsumer對象發送數據 14
點對點消息:

1 <template> 2 <div> 3 <input type="text" v-model="message"> 4 <input type="text" v-model="user"> 5 6 <p><input type="button" @click="send" value="發送"></p> 7 <p><input type="button" @click="close_socket" value="關閉"></p> 8 </div> 9 </template> 10 11 12 <script> 13 export default { 14 name:'websocket1', 15 data() { 16 return { 17 message:'', 18 testsocket:'', 19 user:'' 20 } 21 }, 22 methods:{ 23 send(){ 24 25 // send 發送信息 26 // close 關閉連接 27 var data1 = {"message":this.message,"to_user":this.user} 28 29 this.testsocket.send(JSON.stringify(data1)) 30 this.testsocket.onmessage = (res) => { 31 console.log("WS的返回結果",res.data); 32 } 33 34 }, 35 close_socket(){ 36 this.testsocket.close() 37 }, 38 generate_uuid: function() { 39 var d = new Date().getTime(); 40 if (window.performance && typeof window.performance.now === "function") { 41 d += performance.now(); //use high-precision timer if available 42 } 43 var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( 44 /[xy]/g, 45 function(c) { 46 var r = (d + Math.random() * 16) % 16 | 0; 47 d = Math.floor(d / 16); 48 return (c == "x" ? r : (r & 0x3) | 0x8).toString(16); 49 } 50 ); 51 return uuid; 52 }, 53 54 }, 55 mounted(){ 56 var username = this.generate_uuid(); 57 console.log(username) 58 this.testsocket = new WebSocket("ws://127.0.0.1:8000/ws/"+ username +"/") 59 console.log(this.testsocket) 60 61 this.testsocket.onmessage = (res) => { 62 console.log("WS的返回結果",res.data); 63 } 64 65 // onopen 定義打開時的函數 66 // onclose 定義關閉時的函數 67 // onmessage 定義接收數據時候的函數 68 // this.testsocket.onopen = function(){ 69 // console.log("開始連接socket") 70 // }, 71 // this.testsocket.onclose = function(){ 72 // console.log("socket連接已經關閉") 73 // } 74 } 75 } 76 </script>

1 from channels.generic.websocket import WebsocketConsumer 2 user_dict ={} 3 list = [] 4 import json 5 class ChatService(WebsocketConsumer): 6 # 當Websocket創建連接時 7 def connect(self): 8 self.accept() 9 username = self.scope.get("url_route").get("kwargs").get("username") 10 user_dict[username] =self 11 print(user_dict) 12 13 # list.append(self) 14 15 16 # 當Websocket接收到消息時 17 def receive(self, text_data=None, bytes_data=None): 18 data = json.loads(text_data) 19 print(data) 20 to_user = data.get("to_user") 21 message = data.get("message") 22 23 ws = user_dict.get(to_user) 24 print(to_user) 25 print(message) 26 print(ws) 27 ws.send(text_data) 28 29 30 # 當Websocket發生斷開連接時 31 def disconnect(self, code): 32 pass