django channels 异步demultiplexer 客户端收到不到消息

实时 Django 终于来了 —— Django Channels 入门指南 - 技术翻译 - 开源中国社区
实时 Django 终于来了 —— Django Channels 入门指南
【已翻译100%】
英文原文:
推荐于 1年前 (共 17 段, 翻译完成于 03-24)
参与翻译&(2人)&: ,
今天,我们很高兴请到。Jacob是来自Herokai,也是&Django的长期的核心代码贡献者,他将在这里分享一些他对某些特性的深入研究,他认为这些特性将重新定义框架未来。
当Django刚创建时,那是十多年前,网络还是一个不太复杂的地方。大部分的网页都是静态的。由数据库支撑的模型/视图/ 控制器架构的网络应用还是很新鲜的东西。刚刚开始被使用,只在较少的场景中。
到现在2016年,网络明显更加强大。过去的几年里已经看到了所谓的“实时”网络应用:在这类应用中客户端和服务器之间、点对点通信交互非常频繁。包含很多服务(又名微服务)的应用也变成是常态。新的web技术允许web应用程序走向十年前我们只敢在梦里想象的方向。这些核心技术之一就是:一种新的提供全双工通信的协议——一个持久的,允许任何时间发送数据的客户端和服务器之间的连接。
&翻译得不错哦!
在这个新的世界,Django显示出了它的老成。在其核心,Django是建立在请求和响应的简单概念之上的:浏览器发出请求,Django调用一个视图,它返回一个响应并发送回浏览器。
这在WebSockets中是行不通的 !视图的生命周期只在一个请求当中,没有一种机制能打开一个连接不断的发送数据到客户端,而不发送相关请求。
因此:就应运而生了。Channels,简而言之,取代了Django中的“guts” ——请求/响应周期发送跨通道的消息。Channels允许Django以非常类似于传统HTTP的方式支持WebSockets。Channels也允许在运行Django的服务器上运行后台任务。HTTP请求表现以前一样,但也通过Channels进行路由。因此,在Channels 支持下Django现在看起来像这样:
&翻译得不错哦!
如您所见,Django Channels引入了一些新的概念:
Channels基本上就是任务队列:消息被生产商推到通道,然后传递给监听通道的消费者之一。如果你使用Go语言中的渠道,这个概念应该相当熟悉。主要的区别在于,Django Channels通过网络工作,使生产者和消费者透明地运行在多台机器上。这个网络层称为通道层。通道设计时使用Redis作为其首选通道层,虽然也支持其他类型(和API来创建自定义通道层)。有很多整洁和微妙的技术细节,可以看到完整的记录。
现在,搭配使用Django 1.9使用。计划是将通道合并到Django1.10版本,今年夏天将会发布。
我认为Channels将是Django的一个非常重要的插件:它们将支撑Django顺利进入这个新的web开发的时代。虽然这些api还没有成为Django的一部分,他们将很快就会是!所以,现在是一个完美的时间开始学习Channels:你可以了解未来的Django。
&翻译得不错哦!
开始实践:如何在Django中实现一个实时聊天应用
作为一个例子,我构建了一个简单的实时聊天应用程序——就像一个非常非常轻量级的Slack。有很多的房间,每个人都在同一个房间里可以聊天,彼此实时交互(使用WebSockets)。
你可以访问我在,看看在GitHub上的代码,或点击这个按钮来部署自己的。(这需要一个免费的Heroku账户,所以得要):
注意:你需要在点击上面的按钮后,启动工作进程。使用仪表盘或运行heroku ps:scale web=1:free worker=1:free。
如果你想深入了解这个应用程序是如何工作的——包括你为什么需要worker!——那么请继续读下去。我将会一步一步来构建这个应用程序,并突出关键位置和概念。
&翻译得不错哦!
第一步——从Django开始
虽然在实现上有了很大差异,但是这仍旧是我们使用了十年的Django。所以第一步和其他任何Django应用是一样的(如果你是Django新手,你得看看和)。创建一个工程后,你可以定义模型来表示一个聊天室和其中的消息():
class&Room(models.Model):
&&&&name&=&models.TextField()
&&&&label&=&models.SlugField(unique=True)
class&Message(models.Model):
&&&&room&=&models.ForeignKey(Room,&related_name='messages')
&&&&handle&=&models.TextField()
&&&&message&=&models.TextField()
&&&&timestamp&=&models.DateTimeField(default=timezone.now,&db_index=True)
(在这一步中,包括后面的例子,我已经将代码最简化,希望能将焦点放到重点上,全部代码。)
然后创建一个以及相应的和:
def&chat_room(request,&label):
&&&&#&If&the&room&with&the&given&label&doesn't&exist,&automatically&create&it
&&&&#&upon&first&visit&(a&la&etherpad).
&&&&room,&created&=&Room.objects.get_or_create(label=label)
&&&&#&We&want&to&show&the&last&50&messages,&ordered&most-recent-last
&&&&messages&=&reversed(room.messages.order_by('-timestamp')[:50])
&&&&return&render(request,&"chat/room.html",&{
&&&&&&&&'room':&room,
&&&&&&&&'messages':&messages,
现在,我们已经已经有了一个可以运行的Django应用。如果你在标准的Django环境中运行它,你可以看到已经存在的聊天室和聊天记录,但是聊天室内无法进行交互操作。实时没有起作用,我们得做工作来处理&WebSockets。
&翻译得不错哦!
接下来我们做什么
为了搞明白接下来后台需要做些什么,我们得先看下客户端的代码。你可以在&&中找到,其实也没做多少工作!首先,创建一个 websocket:
var&ws_scheme&=&window.location.protocol&==&"https:"&?&"wss"&:&"ws";
var&chat_socket&=&new&ReconnectingWebSocket(ws_scheme&+&'://'&+&window.location.host&+&"/chat"&+&window.location.pathname);
像HTTP和&HTTPS一样,WebSocket协议区分为安全和非安全两种,我们可以按照需要选择合适的.
因为的问题。&我使用了&小技巧&可以在socket断开时自动重连。 (感谢Kenneth Reitz,在他的&例子中指出了这一点)。
接下来,我们将加入一个回调函数,当表单提交时,我们就通过WebSocket发送数据(而不是 POST数据):
$('#chatform').on('submit',&function(event)&{
&&&&var&message&=&{
&&&&&&&&handle:&$('#handle').val(),
&&&&&&&&message:&$('#message').val(),
&&&&chat_socket.send(JSON.stringify(message));
&&&&return&
我们可以通过WebSocket发送任何想要发送的数据。像众多的API一样, JSON 是最容易的,所以我们将要发送的数据打包成JSON格式。
最后,我们需要将回调函数与WebSocket上的新数据接收事件对接起来:
chatsock.onmessage&=&function(message)&{
&&&&var&data&=&JSON.parse(message.data);
&&&&$('#chat').append('&tr&'&
&&&&&&&&+&'&td&'&+&data.timestamp&+&'&/td&'&
&&&&&&&&+&'&td&'&+&data.handle&+&'&/td&'
&&&&&&&&+&'&td&'&+&data.message&+&'&&/td&'
&&&&+&'&/tr&');
简单提示:从获取的信息中拉取数据,在会话的表上加上一行。如果现在就运行这个代码,他是无法运行的,现在还没有谁监听WebSocket连接呢,只是简单的HTTP。现在,让我们来连接WebSocket。
&翻译得不错哦!
安装和创建&Channels
要将这个应用“通道化”,我们需要做三件事情:安装Channels,建立通道层,定义通道路由,修改我们的工程使其运行在Channels上(而不是WSGI)。
1. 安装Channels
要安装Channels,只需要执行pip install channels,然后将 "channels”添加到&INSTALLED_APPS配置项中。安装Channels后,允许Django以“通道模式”运行,使用上面描述的通道架构来完成请求/响应的循环。(为了向后兼容,你仍可以以&WSGI模式运行Django&,但是在这种模式下WebSockets和Channel的其他特性就不能工作了。)
2. 选择一个通道层
接下来,我们将定义一个通道层。这是Channels用来在消费者和生产者(消息发送者)之间传递消息的交换机制。&这是一种有特定属性的消息队列(详细信息请查看)。
我们将使用Redis作为我们的通道层:它是首选的生产型(可用于工程部署)通道层,是部署在Heroku上显而易见的选择。 当然也有一些驻留内存和基于数据的通道层,但是它们更适合于本地开发或者低流量情况下使用。 (更多细节,再次请查看。)
但是首先:因为Redis通道层是在另外的包中实现的,我们需要运行pip安装&asgi_redis。(我将会在下面稍微介绍点“ASGI”。)然后我们在CHANNEL_LAYERS配置中定义通道层:
CHANNEL_LAYERS&=&{
&&&&"default":&{
&&&&&&&&"BACKEND":&"asgi_redis.RedisChannelLayer",
&&&&&&&&"CONFIG":&{
&&&&&&&&&&&&"hosts":&[os.environ.get('REDIS_URL',&'redis://localhost:6379')],
&&&&&&&&},
&&&&&&&&"ROUTING":&"chat.routing.channel_routing",
要注意的是我们把Redis的连接URL放到环境外面,以适应部署到Heroku的情况。
&翻译得不错哦!
3. 通道路由
在通道层(CHANNEL_LAYERS),我们已经告诉 Channel去哪里找通道路由——chat.routing.channel_routing。通道路由很类似与URL路由的概念:URL路由将URL映射到视图函数;通道路由将通道映射到消费者函数。跟&urls.py类似,按照惯例通道路由应该在routing.py里。现在,我们创建一条空路由:
channel_routing&=&{}
(我们将在后面看到好几条通道路由信息,当连接WebSocket的时候回用到。)
你会注意到我们的app里有urls.py和routing.py两个文件:我们使用同一个app处理HTTP请求和WebSockets。这是很典型的做法:Channels应用也是Django应用,所以你想用的所有Django的特性——视图,表单,模型等等——都可以在Channels应用里使用。
&翻译得不错哦!
最后,我们需要替换掉Django的基于HTTP/WSGI的请求处理器,而是使用通道。它是一个基于新兴标准ASGI(异步服务器网关接口)的, 所以我们将在asgi.py文件里定义处理器:
import&channels.asgi
os.environ.setdefault("DJANGO_SETTINGS_MODULE",&"chat.settings")
channel_layer&=&channels.asgi.get_channel_layer()
(将来,Django会自动生成这个文件,就像现在自动生成wsgi.py文件一样。)
现在,如果一切顺利的话,我们应该能在通道上把这个app运行起来。Channels接口服务叫做Daphne,我们可以运行如下命令运行这个app:
$&daphne&chat.asgi:channel_layer&--port&8888
** 如果现在访问http://localhost:8888/ 我们会看到……什么事情也没发生。这很让人困惑,直到你想起Channels将&Django分成了两部分:前台接口服务&Daphne,后台消息消费者。所以想要处理HTTP 请求,我们得运行一个worker:
$&python&manage.py&runworker
现在请求应该能传递过去了。这说明了其中的机制很简洁:Channels 继续处理&HTTP(S)请求,但是是以一个完全不同的方式去处理,这与通过Django运行&&没有太大的不同,那种情况下运行WSGI服务的同时也要运行Celery服务。不过现在,所有的任务——HTTP请求, WebSockets,后台服务都在worker中运行起来了.
(顺便说一句,我们仍然可以通过运行python manage.py runserver命令来做本地测试。当这么做时, Channels只是在同一进程里运行起Daphne和一个worker。)
&翻译得不错哦!
WebSocket消费者
好了,我们已经完成了安装;让我们开始进入最奇妙的部分吧。
Channels 将WebSocket连接映射到三个通道中:
一个新的客户端 (如浏览器)第一次通过WebSocket连接上时,一条消息被发送到&websocket.connect 通道。当这发生时,我们记录这个客户端当前进入一个已知的聊天室。
每条客户端通过已建立的socket发送的消息都被发送到&websocket.receive通道。(这些都是从浏览器接收到的消息;记住通道都是单向的。我们等一会儿会介绍如何将消息发送给客户端。)当一条消息被接受时,我们将对聊天室里所有其他客户端进行广播。
最后,当客户端断开连接时,一条消息被发送到websocket.disconnect通道。当这发生时,我们将此客户端从聊天室里移除。
首先,我们得在routing.py文件里对这个三个通道进行hook:
from&.&import&consumers
channel_routing&=&{
&&&&'websocket.connect':&consumers.ws_connect,
&&&&'websocket.receive':&consumers.ws_receive,
&&&&'websocket.disconnect':&consumers.ws_disconnect,
其实很简单:就是将每个通道连接到对应的处理函数。现在我们来看看这些函数。按照惯例我们会将这些函数放到一个&consumers.py 文件里(但是像视图一样,其实也可以放在任何地方)。
首先来看看&ws_connect:
from&channels&import&Group
from&channels.sessions&import&channel_session
from&.models&import&Room
@channel_session
def&ws_connect(message):
&&&&prefix,&label&=&message['path'].strip('/').split('/')
&&&&room&=&Room.objects.get(label=label)
&&&&Group('chat-'&+&label).add(message.reply_channel)
&&&&message.channel_session['room']&=&room.label
(为了清晰起见,我将代码中的异常处理和日志去掉了。要看完整版本,请看GitHub上的)。
&翻译得不错哦!
我们的翻译工作遵照 ,如果我们的工作有侵犯到您的权益,请及时联系我们
WebSocket是协议,socket io是实现,两码事
引用来自“Raphael_goh”的评论WebSocket是协议,socket io是实现,两码事我说的是,这篇文章展示的django使用websocket的方法,感觉还不如flask+socketio方便
赶紧用用很快就想丢掉flask了
引用来自“latyas”的评论赶紧用用很快就想丢掉flask了真的么?我用flask做的微信后端,感觉挺好。。。。下次看一下django
引用来自“latyas”的评论赶紧用用很快就想丢掉flask了引用来自“kidfruit”的评论真的么?我用flask做的微信后端,感觉挺好。。。。下次看一下django复杂应用,构建大型应用平台的时候,你就知道django的好处。。。今天,我们很高兴请到 Jacob Kaplan-Moss 。Jacob是
来自Herokai,也是 Django的 长期的核心代码贡献者,他将在这里分享一些他对某些特性的深入研究,他认为这些特性将重新定义框架未来。
当Django刚创建时,那是十多年前,网络还是一个不太复杂的地方。大部分的网页都是静态的。由数据库支撑的模型/视图/ 控制器架构的网络应用还是很新鲜的东西。 Ajax 刚刚开始被使用,只在较少的场景中。
到现在2016年,网络明显更加强大。过去的几年里已经看到了所谓的“实时”网络应用:在这类应用中客户端和服务器之间、点对点通信交互非常频繁。 包含很多服务(又名微服务)的应用也变成是常态。新的web技术允许web应用程序走向十年前我们只敢在梦里想象的方向。这些核心技术之一就是 WebSockets :一种新的提供全双工通信的协议——一个持久的,允许任何时间发送数据的客户端和服务器之间的连接。
在这个新的世界,Django显示出了它的老成。在其核心,Django是建立在请求和响应的简单概念之上的:浏览器发出请求,Django调用一个视图,它返回一个响应并发送回浏览器。
这在WebSockets中是行不通的 !视图的生命周期只在一个请求当中,没有一种机制能打开一个连接不断的发送数据到客户端,而不发送相关请求。
因此: Django Channels 就 应运而生了。Channels,简而言之,取代了Django中的“guts” ——请求/响应周期发送跨通道的消息。Channels允许Django以非常类似于传统HTTP的方式支持WebSockets。Channels也允 许在运行Django的服务器上运行后台任务。HTTP请求表现以前一样,但也通过Channels进行路由。因此,在Channels 支持下Django现在看起来像这样:
如您所见,Django Channels引入了一些新的概念:
Channels基本上就是任务队列:消息被生产商推到通道,然后传递给监听通道的消费者之一。如果你使用Go语言中的渠道,这个概念应该相当熟悉。主要 的区别在于,Django Channels通过网络工作,使生产者和消费者透明地运行在多台机器上。这个网络层称为通道层。通道设计时使用Redis作为其首选通道层,虽然也支持 其他类型(和API来创建自定义通道层)。有很多整洁和微妙的技术细节, 查阅文档 可以看到完整的记录。
现在, 通道作为一个独立的应用程序 搭配使用Django 1.9使用。计划是将通道合并到Django1.10版本,今年夏天将会发布。
我认为Channels将是Django的一个非常重要的插件:它们将支撑Django顺利进入这个新的web开发的时代。虽然这些api还没有成 为Django的一部分,他们将很快就会是!所以,现在是一个完美的时间开始学习Channels:你可以了解未来的Django。
开始实践:如何在Django中实现一个实时聊天应用
作为一个例子,我构建了一个简单的实时聊天应用程序——就像一个非常非常轻量级的Slack。有很多的房间,每个人都在同一个房间里可以聊天,彼此实时交互(使用WebSockets)。
你可以访问我在 网络上部署的例子 ,看看在GitHub上的代码,或点击这个按钮来部署自己的。(这需要一个免费的Heroku账户,所以得要 先注册 ):
注意:你需要在点击上面的按钮后,启动工作进程。使用仪表盘或运行heroku ps:scale web=1:free worker=1:free。
如果你想深入了解这个应用程序是如何工作的——包括你为什么需要worker!——那么请继续读下去。我将会一步一步来构建这个应用程序,并突出关键位置和概念。
第一步——从Django开始
虽然在实现上有了很大差异,但是这仍旧是我们使用了十年的Django。所以第一步和其他任何Django应用是一样的(如果你是Django新手,你得看看 如何在Heroku上开始使用
和 Django新手教程 )。创建一个工程后,你可以定义模型来表示一个聊天室和其中的消息( chat/models.py ):
Room(models.Model):
models.TextField()
models.SlugField(unique
Message(models.Model):
models.ForeignKey(Room,related_name
'messages'
models.TextField()
models.TextField()
models.DateTimeField(default
timezone.now,db_index
(在这一步中,包括后面的例子,我已经将代码最简化,希望能将焦点放到重点上,全部代码 请看Gitbub 。)
然后创建一个 聊天室视图 以及相应的 urls.py 和 模板 :
chat_room(request,label):
#Iftheroomwiththegivenlabeldoesn'texist,automaticallycreateit
#uponfirstvisit(alaetherpad).
room,created
Room.objects.get_or_create(label
#Wewanttoshowthelast50messages,orderedmost-recent-last
(room.messages.order_by(
'-timestamp'
render(request,
&chat/room.html&
'messages'
:messages,
现在,我们已经已经有了一个可以运行的Django应用。如果你在标准的Django环境中运行它,你可以看到已经存在的聊天室和聊天记录,但是聊天室内无法进行交互操作。实时没有起作用,我们得做工作来处理WebSockets。
接下来我们做什么
为了搞明白接下来后台需要做些什么,我们得先看下客户端的代码。你可以在 chat.js 中找到,其实也没做多少工作!首先,创建一个 websocket:
varws_scheme
window.location.protocol
varchat_socket
newReconnectingWebSocket(ws_scheme
window.location.host
window.location.pathname);
像HTTP和HTTPS一样,WebSocket协议区分为安全和非安全两种,我们可以按照需要选择合适的.
因为 Heroku的路由有60秒钟过期 的问题。我使用了
浏览器WebSocket 小技巧可以在socket断开时自动重连。 (感谢Kenneth Reitz,在他的 Flask WebSocket 例子中指出了这一点)。
接下来,我们将加入一个回调函数,当表单提交时,我们就通过WebSocket发送数据(而不是 POST数据):
'#chatform'
,function(event){
varmessage
message:$(
'#message'
chat_socket.send(JSON.stringify(message));
我们可以通过WebSocket发送任何想要发送的数据。像众多的API一样, JSON 是最容易的,所以我们将要发送的数据打包成JSON格式。
最后,我们需要将回调函数与WebSocket上的新数据接收事件对接起来:
chatsock.onmessage
function(message){
JSON.parse(message.data);
data.timestamp
data.handle
data.message
简单提示: 从获取的信息中拉取数据, 在会话的表上加上一行。如果现在就运行这个代码,他是无法运行的,现在还没有谁监听WebSocket连接呢,只是简单的HTTP。现在,让我们来连接WebSocket。
安装和创建Channels
要将这个应用“通道化”,我们需要做三件事情:安装Channels,建立通道层,定义通道路由,修改我们的工程使其运行在Channels上(而不是WSGI)。
1. 安装Channels
要安装Channels,只需要执行pip install channels,然后将 &channels” 添加到 INSTALLED_APPS配置项中。安装Channels后,允许Django以“通道模式”运行,使用上面描述的通道架构来完成请求/响应的循环。(为了向后兼容,你仍可以以WSGI模式运行 Django ,但是在这种模式下WebSockets和Channel的其他特性就不能工作了。)
2. 选择一个通道层
接下来,我们将定义一个通道层。这是Channels用来在消费者和生产者(消息发送者)之间传递消息的交换机制。这是一种有特定属性的消息队列(详细信息请查看 Channels文档 )。
我们将使用Redis作为我们的通道层:它是首选的生产型(可用于工程部署)通道层,是部署在Heroku上显而易见的选择。 当然也有一些驻留内存和基于数据的通道层,但是它们更适合于本地开发或者低流量情况下使用。 (更多细节,再次请查看 文档 。)
但是首先:因为Redis通道层是在另外的包中实现的,我们需要运行pip安装asgi_redis。(我将会在下面稍微介绍点“ASGI”。)然后我们在CHANNEL_LAYERS配置中定义通道层:
CHANNEL_LAYERS
&asgi_redis.RedisChannelLayer&
:[os.environ.get(
'REDIS_URL'
'redis://localhost:6379'
&chat.routing.channel_routing&
要注意的是我们把Redis的连接URL放到环境外面,以适应部署到Heroku的情况。
3. 通道路由
在通道层(CHANNEL_LAYERS),我们已经告诉 Channel去哪里找通道路由——chat.routing.channel_routing。通道路由很类似与URL路由的概念:URL路由将URL映射到视图函数;通道路由将通道映射到消费者函数。跟urls.py类似,按照惯例通道路由应该在routing.py里。现在,我们创建一条空路由:
channel_routing={}
(我们将在后面看到好几条通道路由信息,当连接WebSocket的时候回用到。)
你会注意到我们的app里有urls.py和routing.py两个文件:我们使用同一个app处理HTTP请求和WebSockets。这是很典型的做法:Channels应用也是Django应用,所以你想用的所有Django的特性——视图,表单,模型等等——都可以在Channels应用里使用。
最后,我们需要替换掉Django的基于HTTP/WSGI的请求处理器,而是使用通道。它是一个基于 新兴标准 ASGI(异步服务器网关接口)的, 所以我们将在asgi.py文件里定义处理器:
channels.asgi
os.environ.setdefault(
&DJANGO_SETTINGS_MODULE&
&chat.settings&
channel_layer
channels.asgi.get_channel_layer()
(将来,Django会自动生成这个文件,就像现在自动生成wsgi.py文件一样。)
现在,如果一切顺利的话,我们应该能在通道上把这个app运行起来。Channels接口服务叫做 Daphne ,我们可以运行如下命令运行这个app:
$daphnechat.asgi:channel_layer
** 如果现在访问http://localhost:8888/ 我们会看到……什么事情也没发生。这很让人困惑,直到你想起Channels将Django分成了两部分:前台接口服务 Daphne ,后台消息消费者。所以想要处理HTTP 请求,我们得运行一个worker:
$pythonmanage.pyrunworker
现在请求应该能传递过去了。这说明了其中的机制很简洁:Channels 继续处理HTTP(S)请求,但是是以一个完全不同的方式去处理,这与通过Django运行
Celery 没有太大的不同,那种情况下运行WSGI服务的同时也要运行Celery服务。不过现在 ,所有的任务——HTTP请求, WebSockets,后台服务都在worker中运行起来了.
(顺便说一句,我们仍然可以通过运行python manage.py runserver命令来做本地测试。当这么做时, Channels只是在同一进程里运行起Daphne和一个worker。)
WebSocket消费者
好了,我们已经完成了安装;让我们开始进入最奇妙的部分吧。
Channels 将WebSocket连接 映射到三个通道中 :
一个新的客户端 (如浏览器)第一次通过WebSocket连接上时, 一条消息被发送到websocket.connect 通道。当这发生时,我们记录这个客户端当前进入一个已知的聊天室。
每条客户端通过已建立的socket发送的消息都被发送到websocket.receive通道。(这些都是从浏览器接收到的消息;记住通道都是单向的。我们等一会儿会介绍如何将消息发送给客户端。)当一条消息被接受时,我们将对聊天室里所有其他客户端进行广播。
最后,当客户端断开连接时,一条消息被发送到websocket.disconnect通道。当这发生时,我们将此客户端从聊天室里移除。
首先,我们得在routing.py文件里对这个三个通道进行hook:
channel_routing
'websocket.connect'
:consumers.ws_connect,
'websocket.receive'
:consumers.ws_receive,
'websocket.disconnect'
:consumers.ws_disconnect,
其实很简单:就是将每个通道连接到对应的处理函数。现在我们来看看这些函数。按照惯例我们会将这些函数放到一个consumers.py 文件里(但是像视图一样,其实也可以放在任何地方)。
首先来看看ws_connect:
channels.sessions
channel_session
@channel_session
ws_connect(message):
prefix,label
Room.objects.get(label
label).add(message.reply_channel)
message.channel_session[
room.label
(为了清晰起见,我将代码中的异常处理和日志去掉了。要看完整版本,请看GitHub上的 consumers.py )。
这里代码很多,让我们一行行来看:
7.客户端将会连接到一个/chat/{label}/形式的WebSocket,label映射的是一个房间的属性。因为所有的WebSocket消息(不考虑URL)客户端都可以在相同的频道里发送和获取消息,我们要在哪个房间工作,通过路径解析就可以。
客户端解析WebSocket路径是通过读取message['path']获得的,这不同于传统的URL路由,Django的urls.py的路由是基于path的。如果你有多个WebSocket URL,你会需要路由到你自己定制的不同函数。(这是一个“早期”频道方面的内容;很可能在未来的版本里
Channel将会包含在 WebSocket URL 路由中 。
8.现在,我们可以从数据库中查看Room对象了。
9.这条线是使聊天功能能工作的关键。我们需要知道如何把消息发送回这个客户端。要做到这点,我们将使用消息的应答通道——每条消息都会有一个应答通道属性(reply_channelattribute),可以用来把消息发送回这个客户端。(我们不需要去自己创建这个通道;Channels已经创建好了。)
然而,只把消息发送到这一个通道还是远远不够的的;当一个用户聊天时,我们想把消息送给每一个连接到此聊天室的用户。要做到这点,我们使用一个通道组( channel group)。一个组是由多个通道连接而成,你可以用他来广播消息。 所以,我们将这个消息的应答通道加入到这个聊天室的特殊通道组中。
10.最后,后续的消息(接收/断开)不再包含这个URL(因为连接已经激活)。所以,我们需要一种方式来把一个WebSocket连接映射到哪个聊天室记录下来。要做到这点,我们可以使用一个通道会话。通道会话很像Django的会话框架: 它们通过通道消息的属性
message.channel_session把这些信息持久化下来。 我们 给一个消费者添加修饰属性
@channel_session,就可以让会话框架起效。(文档见 通道会话如何工作的更多细节 )。
现在一个客户端已经连接上来了,让我们看看ws_receive。WebSocket上每接收一条消息,这个消费者都会被调用:
@channel_session
ws_receive(message):
message.channel_session[
Room.objects.get(label
json.loads(message[
room.messages.create(handle
label).send({
:json.dumps(m.as_dict())})
(再一次说明,为了清晰起见,我把错误处理和日志都去掉了。)
最初的几行很简单:从 channel_session 中解析出聊天室,在数据库中查找出来该聊天室,解析JSON消息,将消息作为Message对象存放在数据库中。然后,我们所要作的就是将这条消息广播给聊天室里所有的成员,为了做到这点我们可以使用和前面一样的通道组。Group.send()将会把这条信息 发送 到加入到本组的所有reply_channel。
然后, ws_disconnect就很简单了:
@channel_session
ws_disconnect(message):
message.channel_session[
label).discard(message.reply_channel)
这里,在从channel session里查找到聊天室后,我们 从聊天组里 断开了reply_channel,就是这样!
部署和扩展
现在我们已经把 WebSockets连接起来并开始工作,我们可以像上面一样运行daphne和worker进行测试,或者运行manage.py runserver)。但是和自己聊天是很寂寞的哦,所以让我们在Heroku上把它跑起来!
大部分情况下
, 一个 Channels 应用和一个Python应用在Heroku上都是一样的—— 在requirements.txt中有 详细需求,
在runtime.txt 定义Python运行事,通过标准的git推送到heroku上进行部署,等等。 (对于一个新手,请看
在Heroku上开始Python开发 教程。) 我将重点突出那些Channel应用和标准Django应用不一样的地方:
1. Procfile 和处理类型
因为Channels应用同时需要 HTTP/WebSocket 服务和一个后台通道消费者,所以Procfile需要定义这两种类型。下面是我们的Procfile:
web:daphnechat.asgi:channel_layer--port$PORT--bind0.0.0.0-v2
worker:pythonmanage.pyrunworker-v2
当我们首次部署,我们需要确认两种处理类型都在运行中(Heroku默认值启动web进程):
:scaleweb=1:
(一个简单的应用将运行在 Heroku的免费或者爱好者层上,不过在实际使用环境中你可能需要升级到产品级来提高吞吐量。)
2. 插件: Postgres和Redis
就像Django的大多数应用,你需要一个数据库, Heroku的Postgres可以完美的满足要求。然而,Channels也需要一个 Redis实例作为通道层。所以,我们在首次部署我们的应用时需要创建一个Heroku Postgres和一个Heroku Redis:
$ heroku addons:create heroku-postgresql$ heroku addons:create heroku-redis
因为Channels实在是太新了,扩展性问题还不是很了解。然而,基于现在的架构和我早前做的一些性能测试,我可以做出一些预测。关键点在于Channels 把负责连接的处理进程(daphne)和负责通道消息处理的处理进程(runworker)分开了。这意味着:
通道的吞吐量——HTTP请求, WebSocket消息,或者自定义的通道消息——取决于工作者进程的数量。所以,如果你需要处理大量的请求,你可以扩展工作者进程(比如,heroku上ps:scale worker=3)。
并发水平——当前打开的连接数——将受限于前端web进程的规模。所以,如果你需要处理大量并发的WebSocket连接,你得扩展web进程(比如, heroku 上ps:scale worker=2)。
基于我前期做的测试工作, 在一个Standard-1X进程内Daphne是非常适合处理成百的并发连接的。所以我估计很少有场景需要扩展这个web进程。一个Channels应用中的工作者进程的个数与一个老风格Django应用所需的web进程个数是相当的。
接下来要做些什么呢?
对WebSocket的支持是Django的一项很大的新特性,但是这只粗浅介绍了Channels可以做些什么。你要记住:Channels是一个运行后台任务的通用工具。因此,很多过去需要 Celery 或者 Python-RQ 才能做得事情,都可以用Channels替换。 Channels无法完全替换复杂的任务队列:他有些很重要的限制,比如只发一次,这并不适合所有的场景。 查看文档以了解全部细节。 当然,Channels可以使通常的后台任务更加简单。比如,你可以很容易的使用Channels完成图像缩略图生成,发送邮件、推文或者短信,运行耗时数据计算等等工作。
对于Channels来说:计划在 Django 1.10中包含 Channels ,定于今年夏天发布。这意味着现在是一个很好的时机来尝试一下并给出反馈:您的反馈将会推动这一重要特性的发展方向。如果你想参与进来,看看这份指导文档 向Djang贡献代码 , 然后到 django开发者邮件列表 里分享你的反馈。
最后: 非常感谢 Andrew Godwin 在 Channels上付出的努力工作。这真是Django的一个非常激动人心的新方向,我很激动地看到它开始发展起来。
进一步阅读
关于Channels的更多信息,请查看 Channels文档 ,其中包含很多细节和引用,包括:
Channels的 FAQs 答案。
将 Channels整合进Django 的计划。
正式的 异步网管接口说明 (如果你真的想要了解所有技术细节!)
关于在 Heroku上使用 Python 的信息 ,请访问 Python on Heroku in Dev Center 。我推荐其中的几篇特别好的文章:
Getting Started with Python on Heroku
Configuring Django apps for Heroku
&函数式swift&读书笔记之Tri
最新教程周点击榜
6读书笔记之Trie结构
微信扫一扫

我要回帖

更多关于 django channels 教程 的文章

 

随机推荐