WebSocket介绍与WebSocket在Django3中的实现

关于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协议的被动性。这样非常消耗资源

轮询 需要服务器有很快的处理速度和资源。长轮询 需要有很高的并发。

长连接型:

基于socket长连接,由于长连接是双向且有状态的保持连接,所以服务端可以有效的主动的向客户端推送数据

1.socketio长连接协议

优点:消息即时,兼容性强

缺点:接入复杂度高,为保障兼容性冗余依赖过多较重

2.websocket长连接协议

优点:消息即时,轻量级,灵活适应多场景,机制更加成熟

缺点:相比socket兼容性较差

 
客户端:喂 有新的信息吗

服务端:emmm 没有 等有了就给你!

客户端:那麻烦你了!

服务端:没事哦

服务端:来啦来啦 有新的消息

服务端:神奇宝贝不神奇了是什么?

客户端:收到 宝贝

客户端:嘻嘻嘻
 

经过一次 HTTP 请求,就可以源源不断信息传送!

总体来说,Socketio紧紧只是为了解决通讯而存在的,而Websocket是为了解决更多更复杂的场景通讯而存在的

这里推荐Websocket的原因是因为,我们的Django框架甚至是Flask框架,都有成熟的第三方库

而且Tornado框架集成Websocket。

Django实现WebSocket:

大概流程:

  1. 下载

  2. 注册到setting.py里的app

  3. 在setting.py同级的目录下注册channels使用的路由----->routing.py

  4. 将routing.py注册到setting.py

  5. 把urls.py的路由注册到routing.py里

  6. 编写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"

3.启动带有Channels提供的ASGIDjango项目

出现以下情况:
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.

很明显可以看到 ASGI/Channels, 这样就算启动完成了

4.创建Websocket服务

1.创建一个新的应用chats

python manage.py startapp chats

2.在settings.py中注册chats

1 INSTALLED_APPS = [
2     'chats',
3     'channels'
4 ]

3.在chats应用中新建文件chatService.py

 
 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
 

5.为Websocket处理对象增加路由

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文件中增加ASGIHTTP请求处理

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

websocket客户端:

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 关闭连接
2425             this.testsocket.send(this.message)
26             this.testsocket.onmessage = (res) => {
27                 console.log("WS的返回结果",res.data);         
28             }
2930         },
31         close_socket(){
32             this.testsocket.close()
33         }
3435     },
36     mounted(){
37         this.testsocket = new WebSocket("ws://127.0.0.1:8000/ws/") 
383940         // 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 ​

点对点消息:

客户端将用户名拼接到url,并在发送的消息里指明要发送的对象

WebSocket介绍与WebSocket在Django3中的实现
 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>

服务端存储用户名以及websocketConsumer,然后给对应的用户发送信息

WebSocket介绍与WebSocket在Django3中的实现
 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