django channels

pip3 install django
pip3 install channels
安裝django及channels

建立channels庫根路由配置文件,根路由配置文件相似Django URLconf,它會告訴Channels當收到由Channes服務器發過來的Http請求時,應該執行什麼代碼:html

# wssite/routing.py
from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
    # (http->django views is added by default)
})

將 Channels 庫添加到已安裝的應用程序列表中。編輯 wssite/settings.py 文件並將 'channels' 添加到 INSTALLED_APPS 設置:web

# mysite/settings.py
INSTALLED_APPS = [
    'channels',
    'chat',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

配置根路由指向 Channels:redis

# wssite/settings.py
# Channels
ASGI_APPLICATION = 'wssite.routing.application'

如今已安裝的應用程序中有 Channels, 它將控制 runserver 命令, 用 Channels 開發服務器替換標準的 Django 開發服務器。數據庫

consumer配置

在app中建立新文件consumer,並添加以下代碼:django

# app/consumers.py
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=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        self.send(text_data=json.dumps({
            'message': message

這是一個同步 WebSocket consumer, 它接受全部鏈接, 接收來自其客戶端的消息, 並將這些消息回送到同一客戶端。如今, 它不向同一個房間的其餘客戶端廣播消息。json

Channels 還支持編寫異步 consumers 以提升性能。可是, 任何異步 consumers 都必須當心, 避免直接執行阻塞操做。安全

咱們須要爲 app 建立一個路由配置, 它有一個通往 consumer 的路由。建立新文件 app/routing.py。並寫入如下代碼:服務器

# app/routing.py
from django.conf.urls import url

from . import consumers

websocket_urlpatterns = [
    url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
]

下一步是將根路由指向 chat.routing 模塊。在 mysite/routing.py 中, 導入 AuthMiddlewareStack、URLRouter 和 chat.routing ;並在 ProtocolTypeRouter 列表中插入一個 "websocket" 鍵, 格式以下:websocket

# app/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

這個根路由配置指定,當與 Channels 開發服務器創建鏈接的時候, ProtocolTypeRouter 將首先檢查鏈接的類型。若是是 WebSocket 鏈接 (ws://或 wss://), 則鏈接會交給 AuthMiddlewareStack。session

AuthMiddlewareStack 將使用對當前通過身份驗證的用戶的引用來填充鏈接的 scope, 相似於 Django 的 AuthenticationMiddleware 用當前通過身份驗證的用戶填充視圖函數的請求對象。而後鏈接將被給到 URLRouter。

根據提供的 url 模式, URLRouter 將檢查鏈接的 HTTP 路徑, 以將其路由指定到到特定的 consumer。

channel layer

channel layer 是一種通訊系統。它容許多個 consumer 實例互相交談, 以及與 Django 的其餘部分進行通訊。

channel layer 提供如下抽象:

  channel 是能夠發送消息的郵箱。每一個 channel 都有一個名稱。任何有名稱的 channel 均可以向 channel 發送消息。

  group 是一組相關的 channels。group 具備名稱。任何具備名字的 group 均可以按名稱向 group 中添加/刪除 channel, 也能夠向 group 中的全部 channel 發送消息。沒法列舉特定 group 中的 channel。

每一個 consumer 實例都有一個自動生成的惟一的 channel 名稱, 所以能夠經過 channel layer 進行通訊。

使用redis做爲channel layer的後備存儲。

安裝 channels_redis, 以便 Channels 知道如何調用 redis。運行如下命令:

pip3 install channels_redis

在使用 channel layer 以前, 必須對其進行配置。編輯 wssite/settings.py 文件並將 CHANNEL_LAYERS 設置添加到底部:

# wssite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

如今咱們有了一個 channel layer, 讓咱們在 ChatConsumer 中使用它。將如下代碼放在 chat/consumers.py 中, 替換舊代碼:

# app/consumers.py
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']  # scope中包含有關其鏈接的信息且在app/routes.py中的url路由中獲取'room_name'參數
        self.room_group_name = 'chat_%s' % self.room_name # 構建channels_group名稱

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept() # 用於接受websocket鏈接,不調用則表示拒絕接收鏈接

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

異步模式

重寫 ChatConsumer 使其變爲異步的。在 app/consumers.py 中輸入如下代碼:

# app/consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            'message': message
        }))

這些用於 ChatConsumer 的新代碼與原始代碼很是類似, 它們具備如下差別:

  • 如今 ChatConsumer 繼承自 AsyncWebsocketConsumer 而不是 WebsocketConsumer。
  • 全部方法都是 async def, 而不只僅是 def。
  • await 被用於調用執行 I/O 的異步函數。
  • 在 channel layer 上調用方法時, 再也不須要 async_to_sync。

數據庫訪問

Django ORM是一段同步代碼,所以若是您想從異步代碼訪問它,您須要進行特殊處理以確保其鏈接正確關閉。

若是你正在使用SyncConsumer或者基於它的任何東西 - 好比 JsonWebsocketConsumer- 你不須要作任何特別的事情,由於全部代碼都已經在同步模式下運行,而且Channels將做爲SyncConsumer代碼的一部分爲你作清理工做

可是,若是要編寫異步代碼,則須要使用安全的同步上下文調用數據庫方法database_sync_to_async

數據庫鏈接

若是您使用線程使用者(同步的),通道可能會比您可能使用的通道打開更多的數據庫鏈接 - 每一個線程最多能夠打開一個鏈接。

默認狀況下,線程數設置爲「CPU數* 5」,所以您能夠看到最多這個線程數。若是要更改它,請將ASGI_THREADS環境變量設置爲您但願容許的最大數量。

爲了不在鏈接中有太多線程空閒,您能夠改寫代碼以使用異步使用者,而且只在須要使用Django的ORM(使用database_sync_to_async時才進入線程

database_sync_to_async 

要使用它,請在單獨的函數或方法中編寫ORM查詢,而後database_sync_to_async像這樣調用它

from channels.db import database_sync_to_async

async def connect(self):
    self.username = await database_sync_to_async(self.get_name)()

def get_name(self):
    return User.objects.all()[0].name

您也能夠將它用做裝飾者:

from channels.db import database_sync_to_async

async def connect(self):
    self.username = await self.get_name()

@database_sync_to_async
def get_name(self):
    return User.objects.all()[0].name
相關文章
相關標籤/搜索