django channel 的基本使用和概念解析

网上有很多channel的教学文章,我看的时候,总感觉有些概念模糊不清,看得有点头痛(比如推送消息时的type字段究竟时什么意思)。结合多篇文章,很不容易理解了一些,于是在下面写出channel的基本使用和对一些概念的解析,希望对大家有一些帮助。
关于channel的更深一层理解和使用,我也正在学习,后面可能会写的更详细点。
参考:
1.Django-channels2.0笔记–2、Channel Layers
2.django中channel模块之websocket

1.为什么使用channel

django 本身不支持websocket协议,可以通过使用channel实现对django的支持

2.websocket 搭建

​ 2.1 setting.py 配置

INSTALLED_APPS 中添加 channel,
// 'Chat.wsgi.application' 是websock路由配置的地方,根据实际修改
WSGI_APPLICATION = 'Chat.wsgi.application',
// 配置redis作为通道层,可选
// 如果要使用channel layer 则必须配置通道层,否则无法获取channel_name
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('localhost', 6379)],
        },
    },
}

​ 2.2 约定俗成地新建一个routing.py(路径与WSGI_APPLICATION 中一致)

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chartroom.routing

application = ProtocolTypeRouter({
	// 这里只对websocket通信进行配置,还可以对其他通信进行处理
    'websocket': AuthMiddlewareStack(
        URLRouter(
         	// 实际的websock请求会路由到这里
            chartroom.routing.websocket_urlpatterns
        )
    ),
})

chartroom.routing.websocket_urlpatterns 如下:
from django.urls import path
from chartroom.consumers import ChatConsumer

websocket_urlpatterns = [
	// 按需要自己进行配置,与django的类视图一致
	// Chatconsumer 是自定义的消费者类
    path('ws/chat/', ChatConsumer),
]
  1. 消费者类的编写
class ChatConsumer(WebsocketConsumer):
     # 连接时触发
    def connect(self):
    	// 将channel_name进行分组,后面可以通过组名来分发消息给同一个组的所有channel
    	// 这里特别说明一下,组名是自定义的,自行对channel进行分组
        async_to_sync(self.channel_layer.group_add)(
            "lkm",
            self.channel_name
        )
        print(self.channel_name)
        // 将连接的websock的channel_name 进行持久化,通常与用户名进行绑定
        // 只有定义了通道层(如redis),才能获取到channel_name,否则会报错
        c = Clients(channel_name=self.channel_name)
        c.save()
        self.accept()

    # 关闭连接时触发
    def disconnect(self, close_code):
        pass

	# 收到消息后触发
    def receive(self, text_data):
		// 自动将消息发送到当前连接的websocket客户端
        self.send(text_data=text_data)
	
	// event 相当于request
    def chat_message(self, event):
        print("chat_message")
        self.send(text_data=event["text"])
 
系统会根据连接状态先后执行connect,disconnect等方法,这些方法名是固定的
chat_message 方法名是自定义的

4.在消费者类外进行消息推送

在消费者类外进行消息推送,推送消息给多个客户端需要利用channel layer
Channel引入了一个layer的概念,channel layer是一种通信系统,允许多个consumer实例之间互相通信,以及与外部Django程序实现互通。

Channel layer主要实现了两种概念抽象:
channel name:
channel实际上是一个发送消息的通道,每个channel都有一个名称,每一个拥有这个名称的人都可以往channel里面发送消息

group:
多个channel可以组成一个group,每个group都有一个名称。
每个拥有这个名称的人都可以往这个group里添加/删除channel,也可以往group里发送消息。
group内的所有channel都可以收到,但是不能给group内的具体某个channel发送消息。

================================================================================

from channels.layers import get_channel_layer
from .models import Clients
def chat1(request):
    // 取出保存的channel_name
    c = Clients.objects.all()[0]
    // 获取当前的通道层对象
    channel_layer = get_channel_layer()
    // 发送到指定的channel,并将消息转发到type中的方法进行处理
    async_to_sync(channel_layer.send)(c.channel_name, {
         "type": "chat.message",
         "text": "Hello there!",
     })
     
    // 发送到组里所有的channel
    async_to_sync(channel_layer.group_send)("lkm",{
        "type": "chat_message",
        "text": "xxx!",
    })
    return HttpResponse("ok")

注意:
   1.因为layer中所有的方法都是异步的,要从同步环境发送事件,需要使用async_to_sync 来包装,否	   则消息无法发送。
   2.type含义:将该消息转发到对应消费者类中的对应方法进行处理,chat.message 会被解析为chat_mesage, 然后将消息转发到chat_message中进行下一步处理。所以要注意方法的准确命名。至于怎么准确找到对应消费者,我认为应该是channel_name 里应该就包含了相关信息。
   3.一个连接(channel)创建时,通过group_add将channel添加到Group中。
	连接关闭时,通过group_discard将channel从Group中删除。
	收到消息时,调用group_send发送消息到Group中,group中的所有channel都可以收到。
  1. 直接在消费者类为同步

    from channels.generic.websocket import AsyncWebsocketConsumer,WebsocketConsumer
    import json
    from asgiref.sync import async_to_sync
    from testchannel.tasks import *
    
    class ChatConsumer(AsyncWebsocketConsumer):
        async def connect(self):
            # 连接时触发
            self.group_name = "chat_group"
            await  self.channel_layer.group_add(
                self.group_name, self.channel_name
            )
            await self.accept()
        async def disconnect(self, code):
            # 关闭连接时触发
            await self.channel_layer.group_discard(
                self.group_name, self.channel_name
            )
            pass
        async def receive(self, text_data=None, bytes_data=None):
            # 收到消息后触发
            # 真个ChatConsumer类会将所有接收到的消息加上一个"聊天"的前缀发送给客户端
            text_data_json = json.loads(text_data)
            message = text_data_json['message']
            print("wwwwwwwwwwwww", message)# yyyyyyyyyyyyy
            await self.channel_layer.group_send(
                self.group_name,
                {
                    'type': 'chat_message',
                    'message':message
                }
            )
        async def chat_message(self,event):
            print("enventooooooooooooooooo",event) # {'type': 'chat_message', 'message': 'yyyyyyyyyyyyy'}
            message = "聊天:" + event["message"]
            await self.send(text_data=json.dumps({'message':message}))
    
    class TailfConsumer(WebsocketConsumer):
        def connect(self):
            self.file_id = self.scope["url_route"]["kwargs"]["id"]
            self.result = tailf.delay(self.file_id,self.channel_name)
            # self.result = add.delay(1,8)
            self.accept()
        def disconnect(self, code):
            self.result.revoke(terminate=True)
        def send_message(self,event):
            self.send(text_data=json.dumps({"message":event["message"]}))
            
    有几个小区别:
    ChatConsumer由WebsocketConsumer修改为了AsyncWebsocketConsumer
    所有的方法都修改为了异步defasync def
    用await来实现异步I/O的调用
    channel layer也不再需要使用async_to_sync了
    

    追加

问题:从上面可以知道,以下代码会把消息发送到consumer的chat_message方法再次进行处理,那么,当存在两个个consumer,并且consumer都存在相同方法时,会把改消息转发到哪个consumer的方法上呢?
async_to_sync(channel_layer.group_send)("lkm",{
        "type": "chat_message",
        "text": "xxx!",
    })
我尝试在源码中找到答案,但是水平不够,没找出来,但是通过实验能大概得出以下结论。在建立websocket连接时,会将consumer类与channel  name 进行绑定.当发送信息时,会自动转发到绑定的consumer上。
注意:如果每个consumer中都使用以下代码
async_to_sync(self.channel_layer.group_add)(
            "lkm",
            self.channel_name
  )
 那么,消息会同时转发到两个consumer上的chat_message方法上,因为现在“lkm”这个gruop有两个channel,每个channel绑定一个不同的consumer。但是如果你这样干了,那么你就需要保证每个consumer都有对应chat_message方法,否则会因为某个consumer找不到处理方法而报错。

你可能感兴趣的:(django)