pip3 install django
pip3 install 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 開發服務器。數據庫
在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 是一種通訊系統。它容許多個 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 的新代碼與原始代碼很是類似, 它們具備如下差別:
Django ORM是一段同步代碼,所以若是您想從異步代碼訪問它,您須要進行特殊處理以確保其鏈接正確關閉。
若是你正在使用SyncConsumer
或者基於它的任何東西 - 好比 JsonWebsocketConsumer
- 你不須要作任何特別的事情,由於全部代碼都已經在同步模式下運行,而且Channels將做爲SyncConsumer
代碼的一部分爲你作清理工做。
可是,若是要編寫異步代碼,則須要使用安全的同步上下文調用數據庫方法database_sync_to_async
。
若是您使用線程使用者(同步的),通道可能會比您可能使用的通道打開更多的數據庫鏈接 - 每一個線程最多能夠打開一個鏈接。
默認狀況下,線程數設置爲「CPU數* 5」,所以您能夠看到最多這個線程數。若是要更改它,請將ASGI_THREADS
環境變量設置爲您但願容許的最大數量。
爲了不在鏈接中有太多線程空閒,您能夠改寫代碼以使用異步使用者,而且只在須要使用Django的ORM(使用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