简介

欢迎使用 Channels!

Channels 封装了 Django 原生的异步视图支持,允许 Django 项目不仅处理 HTTP,还处理需要长时间连接的协议——WebSockets、MQTT、聊天机器人、业余无线电等等。

它在保留 Django 同步且易于使用的特性的同时做到了这一点,让你能够选择如何编写代码——以 Django 视图风格的同步方式、完全异步方式,或两者混合的方式。在此基础上,它还提供了与 Django 认证系统、会话系统等的集成,使得将你的纯 HTTP 项目扩展到其他协议比以往任何时候都更容易。

Channels 还将这种事件驱动架构与*通道层*捆绑在一起,这是一个允许你在进程之间轻松通信并将项目分离到不同进程的系统。

如果你还没有安装 Channels,你可能需要先阅读安装 来完成安装。本介绍并非直接的教程,但 你应该能够利用它来跟随并修改现有项目 如果你喜欢,也可以是 Django 项目。

层层深入

Channels 遵循“ 层层深入 ”的原则——我们有一个 关于 Channels “应用”的单一概念,即使是最简单的 消费者 (相当于 Django 视图)是完全有效的 ASGI 应用,你可以单独运行它们。

注意

ASGI 是 Channels 所基于的异步服务器规范的名称。与 WSGI 类似,它旨在让你可以在不同的服务器和框架之间进行选择,而不是被锁定在 Channels 和我们的服务器 Daphne 中。你可以在 https://asgi.readthedocs.io 了解更多信息。

Channels 为你提供了编写这些基本消费者的工具——这些独立的组件可以处理聊天消息或通知——并将它们与 URL 路由、协议检测以及其他便捷功能结合起来,以构建一个完整的应用程序。

我们将 HTTP 和现有的 Django 应用程序视为一个更大整体的一部分。传统的 Django 视图在 Channels 中仍然存在且可用——借助 Django 原生的 ASGI 支持,你也可以编写自定义的 HTTP 长轮询处理或 WebSocket 接收器,并让这些代码与你现有的代码并存。URL 路由、中间件——它们都只是 ASGI 应用程序。

我们相信,对于大多数代码,你希望能够使用安全、同步的技术,如 Django 视图,但对于复杂任务,则可以选择使用更直接、异步的接口。

作用域和事件

Channels 和 ASGI 将传入连接分为两个部分:一个 *作用域* (scope) 和一系列 *事件* (events)。

*作用域* 是一组关于单个传入连接的详细信息——例如发出 Web 请求的路径、WebSocket 的源 IP 地址,或与聊天机器人通信的用户。作用域在整个连接期间持续存在。

对于 HTTP,作用域仅持续一个请求。对于 WebSockets,它持续套接字的生命周期(但如果套接字关闭并重新连接则会改变)。对于其他协议,它根据协议的 ASGI 规范的编写方式而异;例如,聊天机器人协议很可能会在用户与机器人对话的整个过程中保持一个作用域打开,即使底层聊天协议是无状态的。

在此 *作用域* 的生命周期内,会发生一系列 *事件*。这些 代表用户交互——例如,发起一个 HTTP 请求,或者 发送一个 WebSocket 帧。你的 Channels 或 ASGI 应用将 每个作用域实例化一次 ,然后被馈送该作用域内发生的事件流, 以决定采取何种行动。

HTTP 示例:

  • 用户发起 HTTP 请求。

  • 我们打开一个新的 `http` 类型作用域,其中包含请求的路径、方法、标头等详细信息。

  • 我们发送一个 `http.request` 事件,其中包含 HTTP 正文内容

  • Channels 或 ASGI 应用程序处理此事件并生成一个 http.response 事件,以发送回浏览器并关闭连接。

  • HTTP 请求/响应完成,作用域被销毁。

一个聊天机器人示例:

  • 用户向聊天机器人发送第一条消息。

  • 这会打开一个包含用户用户名、所选名称和用户 ID 的作用域。

  • 应用程序会收到一个带有事件文本的 chat.received_message 事件。它不必响应,但如果需要,可以发送一个、两个或更多其他聊天消息作为 chat.send_message 事件。

  • 用户向聊天机器人发送更多消息,并且更多 chat.received_message 事件被生成。

  • 超时后或应用程序进程重启时,作用域关闭。

在一个作用域的生命周期内——无论是聊天、HTTP 请求、套接字连接还是其他——你将有一个应用程序实例处理来自它的所有事件,并且你也可以将数据持久化到应用程序实例上。如果你愿意,你可以选择编写一个原始的 ASGI 应用程序,但 Channels 为你提供了一个易于使用的抽象,称为消费者

什么是消费者?

消费者是 Channels 代码的基本单元。我们称之为“消费者”,因为它 “消费事件”,但你可以将其视为一个微型应用程序。当请求或新套接字传入时,Channels 将遵循其路由表——我们稍后会详细介绍——找到适合该传入连接的消费者,并启动一个副本。

这意味着,与 Django 视图不同,消费者是长期运行的。它们也可以是短期运行的——毕竟,HTTP 请求也可以由消费者提供服务——但它们是围绕着“存活一段时间”这个概念构建的(它们在一个“作用域”的持续时间内存在,如我们上面所述)。

一个基本的消费者看起来像这样:

class ChatConsumer(WebsocketConsumer):

    def connect(self):
        self.username = "Anonymous"
        self.accept()
        self.send(text_data="[Welcome %s!]" % self.username)

    def receive(self, *, text_data):
        if text_data.startswith("/name"):
            self.username = text_data[5:].strip()
            self.send(text_data="[set your username to %s]" % self.username)
        else:
            self.send(text_data=self.username + ": " + text_data)

    def disconnect(self, message):
        pass

每种不同的协议都有不同类型的事件发生,每种类型都由不同的方法表示。你编写代码来处理每个事件,Channels 将负责调度它们并并行运行它们。

在底层,Channels 运行在一个完全异步的事件循环上,如果你编写像上面那样的代码,它将在一个同步线程中被调用。这意味着你可以安全地执行阻塞操作,例如调用 Django ORM:

class LogConsumer(WebsocketConsumer):

    def connect(self, message):
        Log.objects.create(
            type="connected",
            client=self.scope["client"],
        )

然而,如果你想要更多的控制,并且愿意只在异步函数中工作,你可以编写完全异步的消费者:

class PingConsumer(AsyncConsumer):
    async def websocket_connect(self, message):
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, message):
        await asyncio.sleep(1)
        await self.send({
            "type": "websocket.send",
            "text": "pong",
        })

你可以在消费者中阅读更多关于消费者的信息。

路由和多协议

你可以使用路由将多个消费者(请记住,它们是各自的 ASGI 应用)组合成一个代表你项目的更大的应用:

application = URLRouter([
    path("chat/admin/", AdminChatConsumer.as_asgi()),
    path("chat/", PublicChatConsumer.as_asgi()),
])

Channels 不仅仅是围绕 HTTP 和 WebSockets 构建的——它还允许你将任何协议构建到 Django 环境中,通过构建一个将这些协议映射到一组类似事件的服务器。例如,你可以用类似的方式构建一个聊天机器人:

class ChattyBotConsumer(SyncConsumer):

    def telegram_message(self, message):
        """
        Simple echo handler for telegram messages in any chat.
        """
        self.send({
            "type": "telegram.message",
            "text": "You said: %s" % message["text"],
        })

然后使用另一个路由,让一个项目能够同时处理 WebSocket 和聊天请求:

application = ProtocolTypeRouter({

    "websocket": URLRouter([
        path("chat/admin/", AdminChatConsumer.as_asgi()),
        path("chat/", PublicChatConsumer.as_asgi()),
    ]),

    "telegram": ChattyBotConsumer.as_asgi(),
})

Channels 的目标是让你的 Django 项目能够支持现代网络中可能遇到的任何协议或传输方式,同时让你能够沿用熟悉的组件和编码风格。

有关协议路由的更多信息,请参阅路由

跨进程通信

与标准 WSGI 服务器非常相似,处理协议事件的应用程序代码在服务器进程本身内部运行——例如,WebSocket 处理代码在 WebSocket 服务器进程内部运行。

您的整体应用程序的每个套接字或连接都由 这些服务器中的一个**应用程序实例**处理。它们被调用,可以直接将数据发送回客户端。

然而,随着您构建更复杂的应用程序系统,您开始需要在不同的**应用程序实例**之间进行通信——例如,如果您正在构建一个聊天室,当一个**应用程序实例**收到一条传入消息时,它需要将其分发给聊天室中代表人员的任何其他实例。

您可以通过轮询数据库来做到这一点,但 Channels 引入了**通道层**的概念,这是一种围绕一组传输的低级抽象,允许您在不同进程之间发送信息。每个应用程序实例都有一个唯一的**通道名称**,并且可以加入**组**,从而实现点对点和广播消息传递。

注意

信道层是 Channels 的一个可选部分,如果你愿意,可以禁用它(通过将 CHANNEL_LAYERS 设置为空值)。

# In a consumer
self.channel_layer.send(
    'event',
    {
        'type': 'message',
        'channel': channel,
        'text': text,
    }
)

你也可以向一个专用的进程发送消息,该进程正在监听其自身的固定通道名称:

# In a consumer
self.channel_layer.send(
    "myproject.thumbnail_notifications",
    {
        "type": "thumbnail.generate",
        "id": 90902949,
    },
)

你可以在通道层中阅读更多关于通道层的内容。

Django 集成

Channels 附带了对常见 Django 功能(如会话和身份验证)的简单即插即用支持。你只需在 WebSocket 视图周围添加正确的中间件,即可将身份验证与它们结合使用:

from django.core.asgi import get_asgi_application
from django.urls import re_path

# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter([
                re_path(r"^front(end)/$", consumers.AsyncChatConsumer.as_asgi()),
            ])
        )
    ),
})

欲了解更多,请参阅会话认证