Django与Channels实现WebSocket
文章目录
- WebSocket
- ajax轮询
- long poll
- Websocket
- Channels
- WSGI
- ASGI
- Django中使用
- 信息交互的周期
- 前端实现WebSocket
- 前后端分离项目实现Websocket
WebSocket
在讲Websocket
之前,先了解下 long poll
和 ajax轮询
的原理。
ajax轮询
ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
long poll
long poll
其实原理跟 ajax
轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。
ajax
轮询 需要服务器有很快的处理速度和资源(速度)。long poll
需要有很高的并发,也就是说同时接待客户的能力(场地大小)。
Websocket
WebSocket
是一种在单个TCP
连接上进行全双工通讯的协议。WebSocket
允许服务端主动向客户端推送数据。在WebSocket
协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输。
WebSocket
的请求头中重要的字段:
Connection
和Upgrade
:表示客户端发起的WebSocket
请求Sec-WebSocket-Version
:客户端所使用的WebSocket
协议版本号,服务端会确认是否支持该版本号Sec-WebSocket-Key
:一个Base64
编码值,由浏览器随机生成,用于升级request
WebSocket
的响应头中重要的字段:
HTTP/1.1 101 Swi tching Protocols
:切换协议,WebSocket
协议通过HTTP
协议来建立运输层的TCP
连接Connection
和Upgrade
:表示服务端发起的WebSocket
响应Sec-WebSocket-Accept
:表示服务器接受了客户端的请求,由Sec-WebSocket-Key
计算得来
WebSocket
协议的优点:
- 支持双向通信,实时性更强
- 数据格式比较轻量,性能开销小,通信高效
- 支持扩展,用户可以扩展协议或者实现自定义的子协议(比如支持自定义压缩算法等)
WebSocket
协议的优点:
- 少部分浏览器不支持,浏览器支持的程度与方式有区别
- 长连接对后端处理业务的代码稳定性要求更高,后端推送功能相对复杂
- 成熟的
HTTP
生态下有大量的组件可以复用,WebSocket
较少
WebSocket
的应用场景:
- 即时聊天通信,网站消息通知
- 在线协同编辑,如腾讯文档
- 多玩家在线游戏,视频弹幕,股票基金实施报价
Channels
Django
本身不支持WebSocket
,但可以通过集成Channels
框架来实现WebSocket
Channels
是针对Django项目的一个增强框架,可以使Django
不仅支持HTTP
协议,还能支持WebSocket
,MQTT
等多种协议,同时Channels
还整合了Django
的auth
以及session
系统方便进行用户管理及认证。
channels中文件和配置的含义
asgi.py
:介于网络协议服务和Python
应用之间的接口,能够处理多种通用协议类型,包括HTTP
、HTTP2
和WebSocket
channel_layers
:在settings.py中配置。类似于一个通道,发送者(producer)在一段发送消息,消费者(consumer)在另一端进行监听routings.py
:相当于Django中的urls.py
consumers.py
:相当于Django中的views.py
WSGI
WSGI(Python Web Server Gateway Interface)
:为Python语言定义的Web服务器和Web
应用程序或者框架之间的一种简单而通用的接口。
ASGI
ASGI(Asynchronous Web Server Gateway Interface)
:异步网关协议接口,一个介于网络协议服务和Python
应用之间的标准接口,能够处理多种通用的协议类型,包括HTTP
,HTTP2
和WebSocket
。
WSGI
是基于HTTP
协议模式的,不支持WebSocket
,而ASGI
的诞生则是为了解决Python
常用的WSGI
不支持当前Web
开发中的一些新的协议标准。同时,ASGI
对于WSGI
原有的模式的支持和WebSocket
的扩展,即ASGI
是WSGI
的扩展。
Django中使用
1、安装channels
,要注意版本的对应,在channels
官网中可以得到对应的django
版本
pip install channels==2.1.7
2、修改settings.py
文件,
# APPS中添加channels
INSTALLED_APPS = ['django.contrib.staticfiles',... ...'channels',
]
# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application' #ASGI_APPLICATION 指定主路由的位置为webapp下的routing.py文件中的application
3、setting.py
的同级目录下创建routing.py
路由文件,routing.py
类似于Django
中的url.py
指明websocket
协议的路由
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
# 第一种设置的方法
from channels.security.websocket import AllowedHostsOriginValidator
application = ProtocolTypeRouter({# 普通的HTTP协议在这里不需要写,框架会自己指明'websocket': AllowedHostsOriginValidator(AuthMiddlewareStack(URLRouter(# 指定去对应应用的routing中去找路由chat.routing.websocket_urlpatterns)),)
})# 第二种设置的方法,需要手动指定可以访问的IP
from channels.security.websocket import OriginValidator
application = ProtocolTypeRouter({# 普通的HTTP协议在这里不需要写,框架会自己指明'websocket': OriginValidator(AuthMiddlewareStack(URLRouter(# 指定去对应应用的routing中去找路由chat.routing.websocket_urlpatterns)),# 设置可以访问的IP列表['*'])
})
ProtocolTypeRouter:ASIG
支持多种不同的协议,在这里可以指定特定协议的路由信息,我们只使用了websocket
协议,这里只配置websocket
即可
AllowedHostsOriginValidator:指定允许访问的IP
,设置后会去Django
中的settings.py
中去查找ALLOWED_HOSTS
设置的IP
AuthMiddlewareStack:用于WebSocket
认证,继承了Cookie Middleware
,SessionMiddleware,
SessionMiddleware。django
的channels
封装了django
的auth
模块,使用这个配置我们就可以在consumer中通过下边的代码获取到用户的信息
def connect(self):self.user = self.scope["user"]
self.scope
类似于django中的request,包含了请求的type、path、header、cookie、session、user等等有用的信息
URLRouter: 指定路由文件的路径,也可以直接将路由信息写在这里,代码中配置了路由文件的路径,会去对应应用下的routeing.py
文件中查找websocket_urlpatterns
chat/routing.py
内容如下
from django.urls import path
from chat.consumers import ChatConsumerwebsocket_urlpatterns = [path('ws/chat/', EchoConsumer), # 这里可以定义自己的路由path('ws/<str:username>/',MessagesConsumer) # 如果是传参的路由在连接中获取关键字参数方法:self.scope['url_route']['kwargs']['username']
]
routing.py
路由文件跟django
的url.py
功能类似,语法也一样,意思就是访问ws/chat/
都交给ChatConsumer
处理。
4、在要使用WebSocket的应用中创建consumers.py
,consumers.py
是用来开发ASGI
接口规范的python
应用,而Django
中的view.py
是用来开发符合WSGI
接口规范的python
应用。
首先了解下面的意思:
event loop
事件循环、event handler
事件处理器、sync
同步、async
异步
下面是一个同步的consumers.py
:
from channels.consumer import SyncConsumerclass EchoConsumer(SyncConsumer):def websocket_connect(self, event):self.send({'type': "websocket.accept" # 这里是固定的写法,type不可以改变,是ASGI的接口规范,})def websocket_receive(self, event):user = self.scope['user'] # 获取当前用户,没有登录显示匿名用户path = self.scope['path'] # Request请求的路径,HTTP,WebSocket# ORM 同步代码 假如要查询数据库user = User.objects.filter(username=username)self.send({"type": "websocket.send", # 这里是固定的写法,type不可以改变"text": event['text'] # 把前端返回过来的text返回回去})
异步的consumers.py
from channels.consumer import AsyncConsumerclass EchoConsumer(AsyncConsumer):async def websocket_connect(self, event):await self.send({'type': "websocket.accept"})async def websocket_receive(self, event):# 在异步中所有的操作都需要异步执行,比如发送请求,操作ORM# 对于异步的请求可以使用模块aiohttp实现异步的request请求# ORM 异步代码 假如要查询数据库# 第一种方式 使用channels通过的模块from channels.db import database_sync_to_asyncuser = await database_sync_to_async(User.objects.filter(username=username))# 第二种方式 使用装饰器@database_sync_to_asyncdef get_username():return User.objects.filter(username=username)await self.send({"type": "websocket.send", "text": event['text'] })
需要注意的是在异步中所有的逻辑都应该是异步的,不可以那同步的和异步的代码混合使用。
继承WebSocketConsumer
的连接
from channels.generic.websocket import AsyncWebsocketConsumerclass MessageConsumer(AsyncWebsocketConsumer):def connect(self):if self.scope['user'].is_anonymous:# 没有登陆的用户直接断开连接self.close()else:# 加入聊天组,并监听对应的频道# self.channel_layer进行监听频道# self.scope['user'].username以用户名作为组名,# self.channel_name 要进行监听的频道,会自己生成唯一的频道self.channel_layer.group_add(self.scope['user'].username,self.channel_name)self.accept()def receive(self, text_data=None, bytes_data=None):'''接受私信'''self.send(text_data=json.dumps(text_data)) # 将接收到的信息返回出去def disconnect(self, code):'''离开聊天组'''# self.scope['user'].username要结束的组名self.channel_layer.group_discard(self.scope['user'].username,self.channel_name)
要改为异步和前面的方法一致
信息交互的周期
项目中可以在视图中直接推送信息给用户
view.py
from channels.layers import get_channel_layer
def send_message(request):... ...channel_layer = get_channel_layer()payload = {'type':'receive', # 这里的写法是固定的,receive代表的是consumers中的receive函数'message':'要发送的信息','sender':sender.username, # 发送者的昵称}channel_layer.group_send(receiver_username,payload)
前端实现WebSocket
WebSocket
对象一个支持四个消息:onopen
,onmessage
,oncluse
和onerror
,我们这里用了两个onmessage和onclose
onopen: 当浏览器和websocket
服务端连接成功后会触发onopen
消息
onerror: 如果连接失败,或者发送、接收数据失败,或者数据处理出错都会触发onerror
消息
onmessage: 当浏览器接收到websocket服务器发送过来的数据时,就会触发onmessage
消息,参数e
包含了服务端发送过来的数据
onclose: 当浏览器接收到websocket服务器发送过来的关闭连接请求时,会触发onclose
消息载请注明出处。
% extends "base.html" %}{% block content %}<textarea class="form-control" id="chat-log" disabled rows="20"></textarea><br/><input class="form-control" id="chat-message-input" type="text"/><br/><input class="btn btn-success btn-block" id="chat-message-submit" type="button" value="Send"/>
{% endblock %}{% block js %}
<script>var chatSocket = new WebSocket('ws://' + window.location.host + '/ws/chat/');chatSocket.onmessage = function(e) {var data = JSON.parse(e.data);var message = data['message'];document.querySelector('#chat-log').value += (message + '\n');};chatSocket.onclose = function(e) {console.error('Chat socket closed unexpectedly');};document.querySelector('#chat-message-input').focus();document.querySelector('#chat-message-input').onkeyup = function(e) {if (e.keyCode === 13) { // enter, returndocument.querySelector('#chat-message-submit').click();}};document.querySelector('#chat-message-submit').onclick = function(e) {var messageInputDom = document.querySelector('#chat-message-input');var message = messageInputDom.value;chatSocket.send(JSON.stringify({'message': message}));messageInputDom.value = '';};
</script>
{% endblock %}
参考博客:https://juejin.im/post/5cb67fc3e51d456e6a1d0237
前后端分离项目实现Websocket
环境版本:django==2.0channels==2.2.0channels-redis==2.3.2
vue实现代码:
全局配置 websocket.js
const path = window.location.host
const WSS_URL = 'wss://' + path + '/ws/chat/'
let Socket = ''
let setIntervalWebsocketPush = null/** 建立连接 */
export function createSocket(projectId) {if (!Socket) {console.log('建立websocket连接')Socket = new WebSocket(WSS_URL + projectId)Socket.onopen = onopenWSSocket.onmessage = onmessageWSSocket.onerror = onerrorWSSocket.onclose = oncloseWS} else {console.log('websocket已连接')}
}
/** 打开WS之后发送心跳 */
export function onopenWS() {sendPing() // 发送心跳
}
/** 连接失败重连 */
export function onerrorWS() {clearInterval(setIntervalWebsocketPush)Socket.close()createSocket() // 重连
}
/** WS数据接收统一处理 */
export function onmessageWS(e) {window.dispatchEvent(new CustomEvent('onmessageWS', {detail: e.data}))
}
/** 发送数据 */
export function sendWSPush(eventTypeArr) {const obj = {appId: 'airShip',cover: 0,event: eventTypeArr}if (Socket !== null && Socket.readyState === 3) {Socket.close()createSocket() // 重连} else if (Socket.readyState === 1) {Socket.send(JSON.stringify(obj))} else if (Socket.readyState === 0) {setTimeout(() => {Socket.send(JSON.stringify(obj))}, 3000)}
}
/** 关闭WS */
export function oncloseWS() {clearInterval(setIntervalWebsocketPush) // 取消由setInterval()设置的timeout。Socket = ''console.log('websocket已断开')
}
/** 发送心跳 */
export function sendPing() {Socket.send('ping')setIntervalWebsocketPush = setInterval(() => {Socket.send('ping')}, 5000)
}
组件内使用 Index.vue
import { createSocket } from '@/api/websocket'
destroyed() {// 根据需要,销毁事件监听window.removeEventListener('onmessageWS', this.getDataFunc)
},
created() {createSocket(projectId)// 添加事件监听window.addEventListener('onmessageWS', this.getDataFunc)
},
methods:{// 监听ws数据响应getDataFunc(e) {const tempData = JSON.parse(e.detail)}
}
django实现代码:
settings.py
# 在应用中注册 channels
# Channels
ASGI_APPLICATION = 'cmdb.routing.application'
CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer","CONFIG": {"hosts": [('127.0.0.1', 6379)],},},
}
consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_syncclass MsgConsumer(WebsocketConsumer):def __init__(self, *args, **kwargs):self.room_group_name = ""super(MsgConsumer, self).__init__(*args, **kwargs)def connect(self):# 链接后将应用id作为组名,project_id = self.scope["path_remaining"]self.room_group_name = project_idasync_to_sync(self.channel_layer.group_add)(self.room_group_name,self.channel_name)self.accept()def disconnect(self, close_code):# 断开连接时从组里面删除async_to_sync(self.channel_layer.group_discard)(self.room_group_name,self.channel_name)def receive(self, text_data=None, bytes_data=None):# 接受到信息时执行text_data_json = json.loads(text_data)message = text_data_json['message']async_to_sync(self.channel_layer.group_send)(self.room_group_name, {'type': 'chat.message', # 必须在MsgConsumer类中定义chat_message'message': message})def send_message(self, event): # 发送信息是执行message = event['message']self.send(text_data=json.dumps({'message': message}))def chat_message(self, event):message = event['message']self.send(text_data=json.dumps({'message': message}))
在其他视图内使用
view.py
# 测试函数
def send_fun():from asgiref.sync import async_to_syncfrom channels.layers import get_channel_layerchannels_layer = get_channel_layer()data = '我是追加的内容\n'for i in [0, 1, 2, 3, 4, 5, 6]:send_dic = {"type": "send.message","message": {'step': i,'content': data}}if i < 5:import timefor j in range(19):time.sleep(0.5)send_dic = {"type": "send.message", # 必须在MsgConsumer类中定义send_message"message": {'step': i,'content': data}}time.sleep(0.5)async_to_sync(channels_layer.group_send)(room_group_name , send_dic)else:async_to_sync(channels_layer.group_send)(room_group_name , send_dic)
Django与Channels实现WebSocket相关推荐
- Django使用Channels实现WebSocket消息通知功能
更多内容请点击 我的博客 查看,欢迎来访. Django Channels https://channels.readthedocs.io/en/latest/installation.html Ch ...
- Python+Django+channels实现websocket
Python+Django+channels实现websocket 前言 公司需要实现一个长连接,用的Python的Django框架.研究了很长时间,发现Django+channels可以实现webs ...
- Django:使用channels创建websocket,实现消息推送
感谢:(1)Django+channels配置与编写:https://blog.csdn.net/weixin_40105587/article/details/87375886 (2)Django+ ...
- django框架channels实现私信功能
这里写自定义目录标题 功能实现 一.安装channels 1. 通过pip在终端安装 2. 将channels加进setting 3. 配置项目默认路由 二.创建私信APP 部分问题 功能实现 一.安 ...
- Django插件Channels ——实现即时通信
注明 本笔记主要参考<Django应用开发实战><Django企业开发实战>,这两本书前者详细,后者精炼.学习之后真的是感觉自己进步了很多.值得一读,如果您遇到了值得一读的书籍 ...
- channels实现websocket实时通讯和消息推送
Django+channels实现websocket实时通讯@channels Django框架集合channels实现实时通讯和消息推送 channel是Django团队的一个研发的一个给Djang ...
- Django中channels的配置
特别注意: 最近在学习django中的websocket的时候遇见了一些配置的问题,被卡了很长的一段时间,所以希望这篇文章可以帮助一些正在学习Djano的小伙伴提供一些解决方法 channels的版本 ...
- Django使用channels进行消息推送
Django使用channels进行消息推送 说明 这个只是说了Django后端的搭建,前端我就不讲了, 可以使用这个网站进行测试 http://www.websocket-test.com/ 环境 ...
- 保姆级别 附带源码 Django集成channels(一)实现简单聊天功能
目录 前言 不想看我瞎BB可以直接跳到这里 1.WebSocket 1.1 ajax轮询 1.2 long poll 1.3 Websocket 2.Channels 2.1 WSGI 2.2 ASG ...
最新文章
- RuntimeException与CheckedException
- 【快应用篇01】快应用它来了!带你了解什么是快应用!
- 仅需一个参数就可搞定OneProxy的VIP机制
- 《Tensorflow 实战》(完整版,附源码)
- Java中的集合HashSet、LinkedHashSet、TreeSet和EnumSet(二)
- python--List extend()方法
- 2016年6月份那些最实用的 jQuery 插件专辑
- appstore ip地址
- 监控工具Zabbix之原理及部署
- 迁移操作系统:如何把系统迁移到固态硬盘SSD?
- WPF字体图标——FontAwesom
- 家用洗地扫地机一体机哪家好、家用小型洗地机推荐
- Windows battery report
- IPsec IKE第一阶段主模式和野蛮模式
- 超实用!手把手教你如何将废旧的 Android 手机改造成一个好用的 Linux 服务器!...
- 计算机金融sci,经济金融类SSCISCI 四区类(垃圾类)杂志汇总,欢迎补充
- 【自学Java】桌球游戏-边界检测,桌球碰撞反弹的实现,以及模拟真实运动,小球减速运动
- SAST——Checkmarx静态检测工具收集(2)
- Oracle 将字符串分割成集合
- matlab C 混编 --- 在MATLAB中使用c语言函数
热门文章
- Spring分布式缓存
- python的核心数据类型_核心数据类型--字符串
- QT4程序在QT5环境编译运行
- Java之主函数——main函数
- Unity制作即时战略游戏毕设
- day11_animal,_eclipse_test,_lianxi,_Obejct
- 电脑发展这么多年,为啥没能革掉鼠标键盘的命?
- android viewpager的使用,ViewPager的基本使用
- 计算机弹钢琴谱子没有加减乘除,学钢琴不是为了学会多少曲子,而是学会举一反三...
- 【哈工大版】动态ReLU:自适应参数化ReLU及Keras代码(调参记录11)