WebSocket介绍与WebSocket在Django中的实现
关于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>
客户端保持不变,同时打开多个客户端
服务端存储每个链接的对象
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