Django使用Channels实现WebSocket--上篇
WebSocket - 开启通往新世界的大门
WebSocket是什么?
WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket允许服务端主动向客户端推送数据。在WebSocket协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输。
WebSocket有什么用?
WebSocket区别于HTTP协议的一个最为显著的特点是,WebSocket协议可以由服务端主动发起消息,对于浏览器需要及时接收数据变化的场景非常适合,例如在Django中遇到一些耗时较长的任务我们通常会使用Celery来异步执行,那么浏览器如果想要获取这个任务的执行状态,在HTTP协议中只能通过轮训的方式由浏览器不断的发送请求给服务器来获取最新状态,这样发送很多无用的请求不仅浪费资源,还不够优雅,如果使用WebSokcet来实现就很完美了
WebSocket的另外一个应用场景就是下文要说的聊天室,一个用户(浏览器)发送的消息需要实时的让其他用户(浏览器)接收,这在HTTP协议下是很难实现的,但WebSocket基于长连接加上可以主动给浏览器发消息的特性处理起来就游刃有余了
初步了解WebSocket之后,我们看看如何在Django中实现WebSocket
Channels
Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket
Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocket,MQTT等多种协议,同时Channels还整合了Django的auth以及session系统方便进行用户管理及认证。
我下文所有的代码实现使用以下python和Django版本
- python==3.6.3
- django==2.2
集成Channels
我假设你已经新建了一个django项目,项目名字就叫webapp
,目录结构如下
project
- webapp
- __init__.py
- settings.py
- urls.py
- wsgi.py
- manage.py
- 安装channels
pip install channels==2.1.7
- 修改settings.py文件,
# APPS中添加channels
INSTALLED_APPS = [
'django.contrib.staticfiles',
'channels',
]
# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application'
channels运行于ASGI协议上,ASGI的全名是Asynchronous Server Gateway Interface。它是区别于Django使用的WSGI协议 的一种异步服务网关接口协议,正是因为它才实现了websocket
ASGI_APPLICATION 指定主路由的位置为webapp下的routing.py文件中的application
- setting.py的同级目录下创建routing.py路由文件,routing.py类似于Django中的url.py指明websocket协议的路由
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({
# 暂时为空,下文填充
})
- 运行Django项目
C:\python36\python.exe D:/demo/tailf/manage.py runserver 0.0.0.0:80
Performing system checks...
Watching for file changes with StatReloader
System check identified no issues (0 silenced).
April 12, 2019 - 17:44:52
Django version 2.2, using settings 'webapp.settings'
Starting ASGI/Channels version 2.1.7 development server at http://0.0.0.0:80/
Quit the server with CTRL-BREAK.
仔细观察上边的输出会发现Django启动中的Starting development server
已经变成了Starting ASGI/Channels version 2.1.7 development server
,这表明项目已经由django使用的WSGI协议转换为了Channels使用的ASGI协议
至此Django已经基本集成了Channels框架
构建聊天室
上边虽然在项目中集成了Channels,但并没有任何的应用使用它,接下来我们以聊天室的例子来讲解Channels的使用
假设你已经创建好了一个叫chat的app,并添加到了settings.py的INSTALLED_APPS中,app的目录结构大概如下
chat
- migrations
- __init__.py
- __init__.py
- admin.py
- apps.py
- models.py
- tests.py
- views.py
我们构建一个标准的Django聊天页面,相关代码如下
url:
from django.urls import path
from chat.views import chat
urlpatterns = [
path('chat', chat, name='chat-url')
]
view:
from django.shortcuts import render
def chat(request):
return render(request, 'chat/index.html')
template:
{% 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 %}
通过上边的代码一个简单的web聊天页面构建完成了,访问页面大概样子如下:
接下来我们利用Channels的WebSocket协议实现消息的发送接收功能
- 先从路由入手,上边我们已经创建了routing.py路由文件,现在来填充里边的内容
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
ProtocolTypeRouter: ASIG支持多种不同的协议,在这里可以指定特定协议的路由信息,我们只使用了websocket协议,这里只配置websocket即可
AuthMiddlewareStack: django的channels封装了django的auth模块,使用这个配置我们就可以在consumer中通过下边的代码获取到用户的信息
def connect(self):
self.user = self.scope["user"]
self.scope
类似于django中的request,包含了请求的type、path、header、cookie、session、user等等有用的信息
URLRouter: 指定路由文件的路径,也可以直接将路由信息写在这里,代码中配置了路由文件的路径,会去chat下的routeing.py文件中查找websocket_urlpatterns,chat/routing.py
内容如下
from django.urls import path
from chat.consumers import ChatConsumer
websocket_urlpatterns = [
path('ws/chat/', ChatConsumer),
]
routing.py路由文件跟django的url.py功能类似,语法也一样,意思就是访问ws/chat/
都交给ChatConsumer
处理
- 接着编写consumer,consumer类似django中的view,内容如下
from channels.generic.websocket import WebsocketConsumer
import json
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = '运维咖啡吧:' + text_data_json['message']
self.send(text_data=json.dumps({
'message': message
}))
这里是个最简单的同步websocket consumer类,connect方法在连接建立时触发,disconnect在连接关闭时触发,receive方法会在收到消息后触发。整个ChatConsumer类会将所有收到的消息加上“运维咖啡吧:”的前缀发送给客户端
- 最后我们在html模板页面添加websocket支持
{% 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, return
document.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 %}
WebSocket对象一个支持四个消息:onopen,onmessage,oncluse和onerror,我们这里用了两个onmessage和onclose
onopen: 当浏览器和websocket服务端连接成功后会触发onopen消息
onerror: 如果连接失败,或者发送、接收数据失败,或者数据处理出错都会触发onerror消息
onmessage: 当浏览器接收到websocket服务器发送过来的数据时,就会触发onmessage消息,参数e
包含了服务端发送过来的数据
onclose: 当浏览器接收到websocket服务器发送过来的关闭连接请求时,会触发onclose消息
- 完成前边的代码,一个可以聊天的websocket页面就完成了,运行项目,在浏览器中输入消息就会通过websocket-->rouging.py-->consumer.py处理后返回给前端
启用Channel Layer
上边的例子我们已经实现了消息的发送和接收,但既然是聊天室,肯定要支持多人同时聊天的,当我们打开多个浏览器分别输入消息后发现只有自己收到消息,其他浏览器端收不到,如何解决这个问题,让所有客户端都能一起聊天呢?
Channels引入了一个layer的概念,channel layer是一种通信系统,允许多个consumer实例之间互相通信,以及与外部Djanbo程序实现互通。
channel layer主要实现了两种概念抽象:
channel name: channel实际上就是一个发送消息的通道,每个Channel都有一个名称,每一个拥有这个名称的人都可以往Channel里边发送消息
group: 多个channel可以组成一个Group,每个Group都有一个名称,每一个拥有这个名称的人都可以往Group里添加/删除Channel,也可以往Group里发送消息,Group内的所有channel都可以收到,但是无法发送给Group内的具体某个Channel
了解了上边的概念,接下来我们利用channel layer实现真正的聊天室,能够让多个客户端发送的消息被彼此看到
- 官方推荐使用redis作为channel layer,所以先安装channels_redis
pip install channels_redis==2.3.3
- 然后修改settings.py添加对layer的支持
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('ops-coffee.cn', 6379)],
},
},
}
添加channel之后我们可以通过以下命令检查通道层是否能够正常工作
>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 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':'https://ops-coffee.cn'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'site': 'https://ops-coffee.cn'}
>>>
- consumer做如下修改引入channel layer
文章未完,全部内容请关注公众号【运维咖啡吧】或个人网站https://ops-coffee.cn查看,运维咖啡吧专注于原创精品内容分享,感谢您的支持
Django使用Channels实现WebSocket--上篇的更多相关文章
- Django使用channels实现Websocket连接
简述: 需求:消息实时推送消息以及通知功能,采用django-channels来实现websocket进行实时通讯.并使用docker.daphne启动通道,保持websocket后台运行 介绍Dja ...
- Django使用Channels实现WebSocket--下篇
希望通过对这两篇文章的学习,能够对Channels有更加深入的了解,使用起来得心应手游刃有余 通过上一篇<Django使用Channels实现WebSocket--上篇>的学习应该对Cha ...
- Django使用channel实现websocket
channel 什么是channel? channel是第三方工具包,对于不支持websocket协议的框架可以借助此包实现websocket 安装 终端安装: pip3 install channe ...
- 6.channels 配置websocket
Django默认不支持websockey,需要Django支持的话需要安装第三方组件 django channels 是django支持websocket的一个模块. 1.安装 pip3 in ...
- Ubuntu + Django(DRF) + channels(websocket)+NGINX + uwsgi 环境部署
原来uwsgi并不能启动 asgi 呀!现在才知道,就因为这一点我花了一周时间才成功啊!!!!!!!! 是呀!你启动uwsgi 是将你的项目启动了,可是你也发现虽然启动了,但是你的websocke ...
- 堡垒机WebSSH进阶之实时监控和强制下线
这个功能我可以不用,但你不能没有 前几篇文章实现了对物理机.虚拟机以及Kubernetes中Pod的WebSSH操作,可以方便的在web端对系统进行管理,同时也支持对所有操作进行全程录像,以方便后续的 ...
- 再聊我们自研的那些Devops工具
两年前我写了篇文章『我们自研的那些Devops工具』介绍了我们自研的一些DevOps工具系统,两年过去了这些工具究竟还有没有在发光发热,又有哪些新的变化呢,我将通过这篇文章来回顾一下这两年的发展与变化 ...
- Django websocket之web端实时查看日志实践案例
这是Django Channels系列文章的第二篇,以web端实现tailf的案例讲解Channels的具体使用以及跟Celery的结合 通过上一篇<Django使用Channels实现WebS ...
- 实时 Django 终于来了 —— Django Channels 入门指南
Reference: http://www.oschina.net/translate/in_deep_with_django_channels_the_future_of_real_time_app ...
随机推荐
- 拉普拉斯平滑处理 Laplace Smoothing
背景:为什么要做平滑处理? 零概率问题,就是在计算实例的概率时,如果某个量x,在观察样本库(训练集)中没有出现过,会导致整个实例的概率结果是0.在文本分类的问题中,当一个词语没有在训练样本中出现,该词 ...
- leveldb 学习记录(六)SSTable:Block操作
block结构示意图 sstable中Block 头文件如下: class Block { public: // Initialize the block with the specified con ...
- 创建 sp
创建 sharepoint solutions---Empty project--module(跟环境中的网站页面一样) element.xml增加pages url=“site pages” 将ws ...
- Visual Studio2013 配置opencv3.3.0 x64系统
注:小白一个,第一次写博客,可能会有一些理解上的错误,只此记录自己测试成功的坎坷之路,已备以后查看,同时给有需要之人. 我是win10 64 位,之前安装了visual studio 2013, 现在 ...
- HQL-Query接口
org.hibernate.Query接口 1.Query接口定义有执行查询的方法(完成HQL语句的解析和执行过程并返回查询结果,就像SQL语句没了JDBC接口就成了一个普通的字符串变量) 2.Que ...
- Java流程控制语句
流程控制语句 内容: if... if...else if...else if...else switch...case for while do...while 分支结构if 接下来要学习的if条件 ...
- lambda 匿名函数
# 普通python函数 def func(a,b,c): return a+b+c print func(1,2,3) # 返回值为6 # lambda匿名函数 f = lambda a,b,c:a ...
- str相关操作
大小写转换:*——记住 * upper() 全大写 title() 首字母大写(只要是不属于英文字母的都是分隔符) 切来切去: center(10,'*') 强行用*在原字符串左右两端拼接,拼接成十个 ...
- getResource()和getResourceAsStream以及路径问题
用JAVA获取文件,听似简单,但对于很多像我这样的新人来说,还是掌握颇浅,用起来感觉颇深,大常最经常用的,就是用JAVA的File类,如要取得c:/test.txt文件,就会这样用File file ...
- JS验证码邮件
js var time = 30; var canSend = true; function f5() { if (canSend) {//判断是否要ajax发送刷新验证码 验证码在后台刷新 //al ...