websocke在django中使用
目录
0、实现服务端和客户端通信
解决方案
轮训,让浏览器每隔2s向后台发送一次请求。缺点: 延迟、请求太多网站压力大
长轮训,客户端向服务端发送请求,服务端最多夯205,一旦有数据到来就立即返回。数据的响应没有延迟。 (大平台、webQQ、web微信)
websocket,客户端和服务端创建连接不断开,那么就可以先实现双向通信。
一、websocket介绍:
websocket,web版的 socket。
原来Web中:
。http协议,无状态&短连接
客户端主动连接服务端
。客户端向服务端发送消息,服务端接收到返回数据
客户端接收到数据。
断开连接
https一些+对数据进行加密
我们在开发过程中想要保留一些状态信息,基于Cookie来做
现在支持:
。 http协议,一次请求一次响应
。websocket协议,创建连持久的连接不断开,基于这个连接可以进行收发数据。[服务端向客户端主动推送消息]o web聊天室
。实时图表,柱状图、饼图 (Highcharts)
channels 4.0之后默认不带Daphne服务器了。
解决方案可以有两种:
1.指定channels的版本为3.x;
2.安装时使用pip3 install -U channels[“daphne”]
http协议
-连接
-数据传输
-断开连接
websocket协议,是建立在http协议之上的。
-连接,客户端发起。0
-握手(验证),客户端发送一个消息,后端接收到消息再做一些特殊处理并返回。服务端支持websocket协议”客户端向服务端发送
GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Praqma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Kev: mnwFxiolctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client max window bits
..
...
r/n/r/n
-服务端接收
mnwFxiolctXFN/DeMt1Amg== 与 magic string 进行拼接magic string =258EAFA5-E914-47DA-95CA-C5ABODC85B11
vl="mnwFxi0lctXFN/DeMt1Amg==”+"258EAFA5-E914-47DA-95CA-C5ABODC85B11"v2 = hmacl(vl)
V3=base64(v2)
HTTP/1.1 101 Switching ProtocolsUpgrade:websocketConnection: UpgradeSec-WebSocket-Accept: 密文
-收发数据 (加密)
b"adasdjf;akidfp;iujas;ldkjfpaisudflkasjd;fkjas;dkjf;aksjdf;ajksd;fjka;sdijkf"
- 先获取第2个字节,8位。10001010
- 再获取第二个字节的后7位0001010 -> payloadlen
- 127,2字节,8个字节,其他字节 (4字节 masking key + 数据)
- 126,2字节,2个字节,其他字节 (4字节 masking key +数据)
- <=125,2字节
- 其他字节 (4字节 masking key + 数据)
- 获取maskingkey,然后对数据进行解密
var DECODED ='';
for (var i = 0; < ENCODED.length; i++) {
(DECODED[I] = ENCODED[i]MASK^[i% 4]
}
-断开连接
二、后端
1. 安装(基于django3.x)
pip3 install channels==3.0.3 -i https://pypi.douban.com/simple
2. settings配置
a. app注册
INSTALLED_APPS = [
'channels',
]
b. 配置变量
ASGI_APPLICATION = "qq_chart.asgi.application"
ASGI_APPLICATION = '当前项目名同名的文件名.asgi.application'
3.修改asgi文件(默认不支持websocket,只支持http)
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from . import routings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'qq_chart.settings')
# application = get_asgi_application()
application = ProtocolTypeRouter({
'http':get_asgi_application(),
'websocket':URLRouter(routings.websocket_urlpatterns)
})
4. 在settings.py同级目录下创建routings.py 路由
from django.urls import re_path
from app01 import consumers
websocket_urlpatterns = [
re_path(r'ws/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi())
]
5.创建websocket视图 consumers.py
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumer
# 异步改成同步
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
'''客户端请求建立链接时 自动触发'''
self.accept() # 建立链接 并且自动帮你维护每一个客户端
def websocket_receive(self, message):
'''客户端发送数据过来 自动触发'''
print(message)
self.send('小心电信诈骗')
def websocket_disconnect(self, message):
'''客户端断开链接之后 自动触发'''
print('断开链接')
raise StopConsumer()
websocket收发数据
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="./js/vue.js"></script>
<title>Title</title>
</head>
<style>
.message {
height: 300px;
border: 1px solid #dddddd;
width: 100%;
}
</style>
<body>
<div class="message" id="message"></div>
<div>
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage();">
<input type="button" value="close" onclick="closeConn();">
</div>
<script>
socket = new WebSocket("ws://127.0.0.1:8000/room/123/")
// 创建好连接之后自动触发(服务端执行self.accept())
socket.onopen = function (event) {
let tag = document.createElement("div");
tag.innerText = "[连接成功]"
document.getElementById("message").appendChild(tag);
}
//服务端主动断开连接时,这个方法也被触发。
socket.onclose = function (event) {
let tag = document.createElement("div");
tag.innerText = "[断开连接]"
document.getElementById("message").appendChild(tag);}
// 当websocket接受到服务端发来的消息时,自动会触发这个函数
socket.onmessage = function (event) {
let tag = document.createElement("div");
tag.innerText = event.data
document.getElementById("message").appendChild(tag);
}
// 发送数据
function sendMessage() {
let tag = document.getElementById('txt');
socket.send(tag.value)
}
function closeConn() {
socket.close(); // 向服务端发生断开连接的请求
}
</script>
</body>
</html>
后端
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumer
# 异步改成同步
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
'''客户端请求建立链接时 自动触发'''
print('有人来连接了')
self.accept() # 建立链接 并且自动帮你维护每一个客户端
# 客户端允许和客户端创建连接(握手)
self.send('小心电信诈骗')
def websocket_receive(self, message):
'''客户端发送数据过来 自动触发'''
text = message['text'] # {'type': 'websocket.receive', 'text': '11'}
print("接收到消息---->", text)
if text == '关闭':
self.close()
# raise StopConsumer() # 如果服务端断开连接时,执行这个方法异常,那么websocket_disconnect方法不再执行
return
res = '{}sb'.format(text)
# 服务端主动关闭连接,给客户端发送一条断开连接的消息
self.send(res)
def websocket_disconnect(self, message):
'''客户端断开链接之后 自动触发'''
print('断开链接')
raise StopConsumer()