在Django中使用Channels功能

lucky_tomato發表於2021-08-17

前言:最近後臺寫遊戲更新版本功能,簡單就是前端傳送更新請求,後端需要對很多臺伺服器進行更新和各種操作,本來想著實現不難,後來發現因為後端需要執行很長時間,前端返回報錯,後端會執行完畢,但是前端先斷開了,這樣在前端頁面我就看不到更新結果了。通過調整nginx引數,設定超時時間,還是日誌會報499狀態碼錯誤。後來瞭解到了websocket,對於需要長時間處理的請求,使用websocket會更好,通過使用websocket實現了自己的功能,簡單分享下

一、什麼是WebSocket

WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。WebSocket允許服務端主動向客戶端推送資料。
在WebSocket協議中,客戶端瀏覽器和伺服器只需要完成一次握手就可以建立永續性的連線,並在瀏覽器和伺服器之間進行雙向的資料傳輸。

WebSocket的響應頭中重要的欄位:
HTTP/1.1 101 Swi tching Protocols:切換協議,WebSocket協議通過HTTP協議來建立運輸層的TCP連線
Connection和Upgrade:表示服務端發起的WebSocket響應
Sec-WebSocket-Accept:表示伺服器接受了客戶端的請求,由Sec-WebSocket-Key計算得來

WebSocket協議的優點:
支援雙向通訊,實時性更強
資料格式比較輕量,效能開銷小,通訊高效
支援擴充套件,使用者可以擴充套件協議或者實現自定義的子協議(比如支援自定義壓縮演算法等)

WebSocket協議的缺點:
少部分瀏覽器不支援,瀏覽器支援的程度與方式有區別
長連線對後端處理業務的程式碼穩定性要求更高,後端推送功能相對複雜
成熟的HTTP生態下有大量的元件可以複用,WebSocket較少

WebSocket的應用場景:
即時聊天通訊,網站訊息通知
線上協同編輯,如騰訊文件
多玩家線上遊戲,視訊彈幕,股票基金實施報價

二、什麼是Channels

Django本身不支援WebSocket,但可以通過整合Channels框架來實現WebSocket
Channels是針對Django專案的一個增強框架,可以使Django不僅支援HTTP協議,還能支援WebSocket,MQTT等多種協議,同時Channels還整合了Django的auth以及session系統方便進行使用者管理及認證。

2.1channels檔案和配置的含義
asgi.py:介於網路協議服務和Python應用之間的介面,能夠處理多種通用協議型別,包括HTTP、HTTP2和WebSocket
channel_layers:在settings.py中配置。類似於一個通道,傳送者(producer)在一段傳送訊息,消費者(consumer)在另一端進行監聽
routings.py:相當於Django中的urls.py
consumers.py:相當於Django中的views.py

2.2channels文件連結

https://channels.readthedocs.io/en/latest/introduction.html

2.3.WSGI和ASGI不同

WSGI(Python Web Server Gateway Interface):為Python語言定義的Web伺服器和Web應用程式或者框架之間的一種簡單而通用的介面。

ASGI(Asynchronous Web Server Gateway Interface):非同步閘道器協議介面,一個介於網路協議服務和Python應用之間的標準介面,能夠處理多種通用的協議型別,包括HTTP,HTTP2和WebSocket。

三、Django中使用Channel

3.1安裝channels

pip install channels==2.1.7

3.2修改setting.py檔案

INSTALLED_APPS = [
    'django.contrib.staticfiles',
    ... ...
    'channels',
]

# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application' #ASGI_APPLICATION 指定主路由的位置為webapp下的routing.py檔案中的application

3.3.setting.py的同級目錄下建立routing.py路由檔案,routing.py類似於Django中的url.py指明websocket協議的路由

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

application = ProtocolTypeRouter({
    'websocket':AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                webapp.routing.websocket_urlpatterns
            )
        )
    )
})

ProtocolTypeRouter:ASGI支援多種不同的協議,在這裡可以指定特定協議的路由資訊,這裡只使用了websocket協議,這裡只配置websocket即可
AllowedHostsOriginValidator:指定允許訪問的IP,設定後會去Django中的settings.py中去查詢ALLOWED_HOSTS設定的IP
AuthMiddlewareStack:用於WebSocket認證,繼承了Cookie Middleware,SessionMiddleware,SessionMiddleware。
django的channels封裝了django的auth模組,使用這個配置我們就可以在consumer中通過下邊的程式碼獲取到使用者的資訊,和請求的url路徑

def connect(self):
    self.user = self.scope["user"]
    self.request_url = self.scope['path']

self.scope類似於django中的request,包含了請求的type、path、header、cookie、session、user等等有用的資訊
URLRouter: 指定路由檔案的路徑,也可以直接將路由資訊寫在這裡,程式碼中配置了路由檔案的路徑,會去對應應用下的routeing.py檔案中查詢websocket_urlpatterns

3.4webapp.routing.py內容如下

from django.urls import path
from webapp.consumers import ChatConsumer

websocket_urlpatterns = [
  path('ws/chat/',ChatConsumer)
]

routing.py路由檔案跟django的url.py功能類似,語法也一樣,意思就是訪問ws/chat/都交給ChatConsumer處理。  

3.5在要使用WebSocket的應用中建立consumers.py,consumers.py是用來開發ASGI介面規範的python應用,而Django中的view.py是用來開發符合WSGI介面規範的python應用。  

from channels.generic.websocket import WebsocketConsumer
from channels.generic.websocket import AsyncWebsocketConsumer
import json,time

Channels支援同步,也支援非同步方式

同步方式程式碼如下:

class ChatConsumer(WebsocketConsumer):
    # websocket建立連線時執行方法
    def connect(self):
        self.accept()
    
    # websocket斷開時執行方法
    def disconnect(self, close_code):
        self.close()
    
    # 從websocket接收到訊息時執行函式
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = f'結果:{text_data_json}'
        self.send(text_data=json.dumps(
        {
             'message': message
        }))

非同步方式程式碼如下:

class ChatConsumer(AsyncWebsocketConsumer):
    #websocket建立連線時執行方法
    async def connect(self):
        await self.accept()

    # websocket斷開時執行方法
    async def disconnect(self, close_code):
        print(close_code)


    # 從websocket接收到訊息時執行函式
    async def receive(self, text_data):
        for i in range(10):
            time.sleep(i)
            message = '結果: ' + str(i)
            await self.send(text_data=json.dumps({
                'message': message
            })
        )

需要注意的是在非同步中所有的邏輯都應該是非同步的,不可以那同步的和非同步的程式碼混合使用。  

四、前端Websocket使用

WebSocket物件一個支援四個訊息:onopen,onmessage,oncluse和onerror

onopen: 當瀏覽器和websocket服務端連線成功後會觸發onopen訊息
onerror: 如果連線失敗,或者傳送、接收資料失敗,或者資料處理出錯都會觸發onerror訊息
onmessage: 當瀏覽器接收到websocket伺服器傳送過來的資料時,就會觸發onmessage訊息,引數e包含了服務端傳送過來的資料
onclose: 當瀏覽器接收到websocket伺服器傳送過來的關閉連線請求時,會觸發onclose訊息

拼接websocket請求地址,建立長連線

var chatSocket = new WebSocket('ws://' + window.location.host + '/ws/ver_update/');

連線事件  

chatSocket.onopen = function () {
    console.log(getCurrentDate(2) + ' ' + 'websocket connection success')
};

錯誤事件

chatSocket.onerror = function () {
    console.error(getCurrentDate(2) + ' ' + 'websocket connection error')
};

關閉事件

chatSocket.onclose = function (e) {
    layer.msg('websocket關閉,檢查錯誤日誌', {icon: 2});
    console.error(getCurrentDate(2) + ' ' + 'websocket closed unexpectedly 狀態碼:' + e.code);
    chatSocket.close();
};

接收事件

chatSocket.onmessage = function (e) {
    var data = JSON.parse(e.data);
}

五、測試Channels功能      

 

 

總結:自從使用Websocket功能後,再也沒發生前端突然斷開的情況了,對於長時間執行的任務,使用websocket是不錯的選擇~,有不足的地方請多多指教  

相關文章