簡述:
需求:消息實時推送消息以及通知功能,采用django-channels來實現websocket進行實時通訊。並使用docker、daphne啟動通道,保持websocket后台運行
介紹Django Channels:
官方文檔鏈接:Django-Channels
#參考 Django Channels 的docs文檔
通道包裝了Django的本機異步視圖支持,允許Djangoprojects不僅處理HTTP,還處理需要長時間運行的連接的協議——網絡套接字、MQTT、聊天機器人、業余無線電等等。
它做到了這一點,同時保留了Django的同步和易於使用的特性,允許您選擇如何編寫代碼——以Django視圖的方式同步,完全異步,或者兩者兼而有之。
除此之外,它還提供了與Django的授權系統、會話系統等的集成,使得將您的純HTTP項目擴展到其他協議比以往任何時候都更容易
#channels由幾個包組成:
Channels,Django集成層
Daphne,HTTP和Websocket終止服務器
asgiref,基本ASGI庫
channels_redis,Redis通道層后端(可選)
依賴性:
這里假設你的django版本是3.0以上,所有channels項目目前都支持Python 3.6及更高版本。
channels與Django 2.2、3.0和3.1兼容。如果你的django版本是2.2或以下,相關配置請查看官網。
安裝django channels:
pip install -U channels #pip 安裝需要加上-U
Django項目設置:
#setting.py添加內容
將Channels庫添加到已安裝的應用程序列表中。編輯 settings.py 文件,並將channels添加到INSTALLED_APPS設置中。
INSTALLED_APPS = [
'channels', #在這里添加,請保持channels在第一項
'appname',
]
ASGI_APPLICATION = "myproject.asgi.application" #就這樣!一旦啟用,通道將自己集成到Django中並控制runserver命令
運行python manage.py runserver 命令:
System check identified no issues (0 silenced).
December 18, 2020 - 09:14:59
Django version 3.1.2, using settings 'myproject.settings'
Starting ASGI/Channels version 3.0.2 development server at http://127.0.0.1:8000/ #<<<請注意這行與之前的區別
Quit the server with CTRL-BREAK.
描述WSGI與ASGI:
篇幅原因,請參看另一篇博文:WSGI與ASGI
邏輯代碼
#創建默認路由(主WS路由)
WS(WebSocket )是不安全的 ,容易被竊聽,因為任何人只要知道你的ip和端口,任何人都可以去連接通訊。
WSS(Web Socket Secure)是WebSocket的加密版本。
所以建議使用WSS,這是測試版使用的是WS
通道提供了對常見Django特性(如會話和身份驗證)的輕松插入支持。只需在WebSocket視圖周圍添加適當的中間件,就可以將身份驗證與WebSocket視圖結合起來
在myproject/myproject下創建routing.py:
from channels.routing import ProtocolTypeRouter,URLRouter
import os
from channels.auth import AuthMiddlewareStack
import appname.routing
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
# (your routes here)
'http' : django_asgi_app,
'websocket': AuthMiddlewareStack(
URLRouter(
appname.routing.websocket_urlpatterns
)
)
})
django2.2配置:
#注:Django 2.2沒有內置的ASGI支持,所以我們需自行在myproject/myproject下創建asgi.py:
import os
import django
from channels.routing import get_default_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
application = get_default_application()
使用Docker安裝和運行Redis:
我們使用Redis作為channel層的后備存儲,這是我們在本教程中使用的Channels庫的可選組件。從Docker的官方網站上
安裝Docker——有MacOS和Windows的官方運行時,使其易於使用,並且有許多Linux發行版的軟件包,可以在本地運行。
#注:使用的通道功能將需要Redis來運行,建議Docker是實現這一點的最簡單方法。
我們將使用一個使用Redis作為后台存儲的通道層。要在端口6379上啟動Redis服務器,請運行以下命令:
docker run -p 6379:6379 -d redis:5
參照圖片:
使用運行協議服務器>Daphne:
注:Daphne是一個純Python ASGI服務器,由Django項目的成員維護。它充當ASGI的參考服務器。
不過django官網上就寥寥幾句話:django-daphne
#安裝 Daphne
python -m pip install daphne
為了與外界對話,需要將Channels/ASGI應用程序加載到協議服務器中。它們可以像WSGI服務器一樣,以HTTP模式運行應用程序,但它們也可以連接到任何數量的其他協議(聊天協議、物聯網協議、甚至無線網絡)。
所有這些服務器都有自己的配置選項,但它們都有一個共同點——它們希望您向它們傳遞一個ASGI應用程序來運行。您只需在項目的asgi.py公司將文件作為它應該運行的應用程序發送到協議服務器:
daphne -b 127.0.0.1 -p 8001 myproject.asgi:application
#使用daphne需單獨安裝redis,並配置添加windos服務,C:\Windows\System32\drivers\etc\hosts添加redis的ip地址,默認是127.0.0.1
運行之后成功參考如下代碼:
(venv) D:\myproject>daphne -b 127.0.0.1 -p 8001 myproject.asgi:application #在你myproject項目下的文件下執行命令
2020-12-18 09:15:55,042 INFO Starting server at tcp:port=8001:interface=127.0.0.1
2020-12-18 09:15:55,042 INFO HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2020-12-18 09:15:55,043 INFO Configuring endpoint tcp:port=8001:interface=127.0.0.1
2020-12-18 09:15:55,050 INFO Listening on TCP address 127.0.0.1:8001
我們需要知道如何安裝redis頻道。運行以下命令:
pip install -U channels_redis
驗證通道是否開啟的方法:
使用 windows + R 打開cmd,輸入:
telnet 127.0.0.1 6379 #6379為端口號,請使用相應的端口測試
telnet通的情況如下:
在使用通道層之前,我們必須對其進行配置。編輯myproject/setting.py文件並在底部添加一個CHANNEL_LAYERS設置。它應該看起來像:
# myproject/settings.py
# Channels
ASGI_APPLICATION = "myproject.asgi.application" #這是之前在setting設置過的,不要重復
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [
('localhost', 6379), #可以配置多個通道層。然而,大多數項目將只使用一個“默認”通道層。
#如果你是使用Docker安裝運行redis的請注釋下面的配置
('redis_server_name', 6379),
#如果你是使用Daphne運行的請注釋掉localhost那一行配置
],
},
},
}
現在我們有了一個通道層,讓我們在chatcustomer中使用它。在chat中輸入以下代碼appname/consumers.py,代碼如下:
# appname/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
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現在繼承了AsyncWebsocketConsumer而不是WebsocketConsumer。所有方法都是異步def,而不僅僅是def。
await用於調用執行I/O的異步函數。在通道層上調用方法時,不再需要異步同步。
應用下創建 routing.py (類似Django路由)
from django.urls import path
from appname import consumers
websocket_urlpatterns = [
# url(r'^ws/msg/(?P<room_name>[^/]+)/$', consumers.SyncConsumer),
path("ws/test_async" , consumers.ChatConsumer.as_asgi()),
]
前端頁面連接Websockct:
templates下新建test/test.html
注:需正常在urls.py添加路由
<<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Websocket測試</title>
<link rel="stylesheet" href="/static/layui/css/layui.css" media="all">
<script src="http://cdn.bootcss.com/jquery/1.12.3/jquery.min.js"></script>
<script src="/static/layui/layui.js"></script>
</head>
<body style="margin: auto">
<script>
if ("WebSocket" in window) {
// 打開一個 web socket
var ws = new WebSocket(+ window.location.host + "/ws/test_async");
console.log("ws:" + window.location.host + "/ws/test_async")
console.log('ws',ws)
ws.onopen = function () {
// Web Socket 已連接上,使用 send() 方法發送數據
alert("鏈接成功")
ws.send("發送消息");
console.log('onopen')
{#alert("數據發送中...");#}
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
{#alert("數據已接收...");#}
console.log("數據:" + received_msg)
};
ws.onclose = function () {
// 關閉 websocket
alert("鏈接關閉,請重試")
console.log("連接已關閉...");
};
}
else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
</script>
網頁上訪問
http://127.0.0.1:8000/test.html
按F12,查看console選項:
或提示消息: