Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket
Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocket,MQTT等多种协议,同时Channels还整合了Django的auth以及session系统方便进行用户管理及认证。
环境准备
Django 3.1.3
channels 2.2.0
channels
https://channels.readthedocs.io/en/stable/
入门
安装
pip install channels
pip install channels_redis
配置
# 安装APP
INSTALLED_APPS = [
'django.contrib.admin',
...
'channels',
# 全局配置 指定ASGI的路由地址
ASGI_APPLICATION = 'DEMO.routing.application'# APP_NAME.routing.application
channels运行于ASGI协议上,ASGI的全名是Asynchronous Server Gateway Interface。它是区别于Django使用的WSGI协议 的一种异步服务网关接口协议,正是因为它才实现了websocket
asgi 与 wsgi
紧跟着需要在根目录创建routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import apps.webapp.routing #.自己的routing
application = ProtocolTypeRouter({
# Empty for now (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
apps.webapp.routing.websocket_urlpatterns # APP下的urlpatterns
)
),
})
创建一个APP
python3 manage.py startapp webapp
app下创建routing.py
from django.conf.urls import url
from .sockets.ChatConsumer import ChatConsumer
websocket_urlpatterns = [
url(r'^chat/$', ChatConsumer),
]
目录结构
views
目录保存当前APP下的HTTP请求
sockets
目录保存SOCKET协议请求
启动项目
后台代码
apps/webapp/sockets/ChatConsumer.py
from channels.generic.websocket import WebsocketConsumer
import json
class ChatConsumer(WebsocketConsumer):
def connect(self):
print("连接...Websocket")
self.accept()
def disconnect(self, code):
print('失去连接...Websocket')
def receive(self, text_data=None, bytes_data=None):
print(text_data)
text_data_json = json.loads(text_data)
message = 'Server: '+ text_data_json['message']
self.send(text_data = json.dumps({
'message': message
}))
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<textarea disabled rows="20" v-model="dataList"></textarea><br/>
<input type="text" v-model="key" /><br/>
<button @click="send">发送</button>
</div>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
<script>
/*
var chatSocket = new WebSocket('ws://127.0.0.1:8000/chat/');
chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data);
var message = data['message'];
document.querySelector('#chat-log').value += (message + '\n');
};*/
var app = new Vue({
el: '#app',
data(){
return {
dataList: '',
key: '',
}
},
methods: {
send(e){
console.log("Send...");
let data = JSON.stringify({
'message': this.key,
})
this.websocket.send(data)
this.key = '';
},
initWebSocket(){
this.websocket = new WebSocket('ws://127.0.0.1:8000/chat/');
this.websocket.onclose = this.websocketClose;
this.websocket.onmessage = this.websocketOnMessage;
this.websocket.onopen = this.websocketOnOpen;
this.websocket.onerror = this.websocketOnError;
},
websocketOnError(e){
console.log(e);
},
websocketOnOpen(e){
console.log(e);
},
websocketOnMessage(e){
console.log(e);
var data = JSON.parse(e.data);
var message = data['message'];
this.dataList += message + '\n';
},
websocketClose(e){
console.log('断开连接', e, e.code, e.wasClean);
}
},
mounted(){
this.initWebSocket();
},
})
</script>
</html>
总结
在根目录下的routing.py
ProtocolTypeRouter
ASIG支持多种不同的协议,在这里可以指定特定协议的路由信息,我们只使用了websocket协议,这里只配置websocket即可
AuthMiddlewareStack
django的channels封装了django的auth模块,使用这个配置我们就可以在consumer中通过下边的代码获取到用户的信息
URLRouter
指定路由文件的路径,也可以直接将路由信息写在这里,代码中配置了路由文件的路径,会去apps.webapp下的routeing.py文件中查找websocket_urlpatterns
前端连接WebSocket
WebSocket对象一个支持四个消息:onopen,onmessage,oncluse和onerror,我们这里用了两个onmessage和onclose
onopen: 当浏览器和websocket服务端连接成功后会触发onopen消息
onerror: 如果连接失败,或者发送、接收数据失败,或者数据处理出错都会触发onerror消息
onmessage: 当浏览器接收到websocket服务器发送过来的数据时,就会触发onmessage消息,参数e包含了服务端发送过来的数据
onclose: 当浏览器接收到websocket服务器发送过来的关闭连接请求时,会触发onclose消息
流程
启用Channel Layer
我们已经实现了消息的发送和接收,但既然是聊天室,肯定要支持多人同时聊天的,当我们打开多个浏览器分别输入消息后发现只有自己收到消息,其他浏览器端收不到,如何解决这个问题,让所有客户端都能一起聊天呢?
Channels引入了一个layer的概念,channel layer是一种通信系统,允许多个consumer实例之间互相通信。
channel layer主要实现了两种概念抽象
channel name: channel实际上就是一个发送消息的通道,每个Channel都有一个名称,每一个拥有这个名称的人都可以往Channel里边发送消息
group: 多个channel可以组成一个Group,每个Group都有一个名称,每一个拥有这个名称的人都可以往Group里添加/删除Channel,也可以往Group里发送消息,Group内的所有channel都可以收到,但是无法发送给Group内的具体某个Channel
pip install channels_redis
电脑安装并且启动Redis
修改配置
# 配置redis作为通道层
# 如果要使用channel layer 则必须配置通道层,否则无法获取channel_name
# settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
测试是否可以
(venv) ➜ DEMO git:(master) ✗ python3 manage.py shell
# 打开终端
import channels.layers # 导入通道
channel_layer = channels.layers.get_channel_layer()
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.send)('test_channel',{'site':'http://taoya.art'})
async_to_sync(channel_layer.receive)('test_channel')
如果报错了,说明Redis服务没有启动成功。
全局通告
总结
代码规范
1. 项目文件下创建routing.py
2. APP下创建的是routing.py