如何基於Django中的WebSockets和非同步檢視來實現實時通訊功能

华为云开发者联盟發表於2024-04-22

本文分享自華為雲社群《結合Django中的WebSockets和非同步檢視實現實時通訊功能的完整指南》,作者: 檸檬味擁抱。

在現代Web應用程式中,實時通訊已經成為了必不可少的功能之一。無論是線上聊天、實時資料更新還是實時通知,都需要透過實時通訊技術來實現。Django作為一個強大的Web框架,提供了許多工具來構建各種型別的Web應用程式,但是在實時通訊方面,傳統的請求-響應模式顯然無法滿足需求。在這篇文章中,我們將探討如何利用Django中的WebSockets和非同步檢視來實現實時通訊功能。

WebSockets簡介

WebSockets是一種在單個TCP連線上提供全雙工通訊的協議。與HTTP請求-響應模式不同,WebSockets允許伺服器和客戶端之間進行持續的雙向通訊,從而實現了實時性。在Django中,我們可以使用第三方庫django-channels來實現WebSocket的支援。

如何基於Django中的WebSockets和非同步檢視來實現實時通訊功能

非同步檢視

Django 3.1引入了非同步檢視的支援,使得我們可以編寫非同步處理請求的檢視函式。這對於處理長時間執行的任務或需要等待外部資源響應的請求非常有用。

結合WebSockets與非同步檢視

下面我們將透過一個案例來演示如何在Django中結合WebSockets和非同步檢視來實現實時通訊功能。假設我們正在開發一個簡單的實時聊天應用。

安裝依賴

首先,我們需要安裝django-channels庫:

pip install channels

配置專案

在專案的settings.py中,新增channels應用:

INSTALLED_APPS = [
    ...
    'channels',
    ...
]

然後,建立一個名為routing.py的新檔案,在其中定義WebSocket路由:

# routing.py

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.urls import path
from myapp.consumers import ChatConsumer

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

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter(
            websocket_urlpatterns
        )
    ),
})

建立Consumer

接下來,我們建立一個消費者(Consumer)來處理WebSocket連線:

# consumers.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = 'chat_room'
        self.room_group_name = f'chat_{self.room_name}'

        # 加入聊天室群組
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # 離開聊天室群組
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

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

        # 傳送訊息到聊天室群組
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    async def chat_message(self, event):
        message = event['message']

        # 傳送訊息給WebSocket連線
        await self.send(text_data=json.dumps({
            'message': message
        }))

編寫前端程式碼

在前端頁面中,我們需要使用JavaScript來連線WebSocket並處理訊息的傳送和接收:

// chat.js

const chatSocket = new WebSocket('ws://localhost:8000/ws/chat/');

chatSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    const message = data['message'];
    // 處理收到的訊息
    console.log(message);
};

chatSocket.onclose = function(e) {
    console.error('Chat socket closed unexpectedly');
};

document.querySelector('#chat-message-input').addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
        const messageInputDom = document.querySelector('#chat-message-input');
        const message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message
        }));
        messageInputDom.value = '';
    }
});

如何基於Django中的WebSockets和非同步檢視來實現實時通訊功能

整合到模板

最後,我們在Django模板中整合JavaScript程式碼:

<!-- chat.html -->

<!DOCTYPE html>
<html>
<head>
    <title>Chat</title>
</head>
<body>
    <textarea id="chat-message-input"></textarea>
    <script src="{% static 'chat.js' %}"></script>
</body>
</html>

引入非同步檢視

在Django 3.1之前,檢視函式都是同步執行的,這意味著一個檢視函式中的程式碼會一直執行直到返回一個HTTP響應給客戶端。然而,有些任務可能是耗時的,比如呼叫外部API或者執行復雜的計算。在這種情況下,同步檢視會阻塞整個應用程式,導致效能下降。

為了解決這個問題,Django引入了非同步檢視,它們使用Python的asyncawait語法來支援非同步程式設計模式。非同步檢視允許在處理請求時掛起執行,等待IO操作完成而不會阻塞整個應用程式。

結合WebSockets與非同步檢視的優勢

結合WebSockets與非同步檢視可以使得實時通訊應用具備更高的效能和可擴充套件性。當有大量連線同時進行通訊時,非同步檢視可以有效地管理這些連線,而不會因為一個連線的阻塞而影響其他連線的處理。這種方式下,應用程式能夠更好地應對高併發情況,保持穩定性和高效性。

如何基於Django中的WebSockets和非同步檢視來實現實時通訊功能

完善實時聊天應用

除了上述示例中的基本聊天功能之外,我們還可以對實時聊天應用進行一些擴充套件,比如:

  1. 使用者認證:在連線WebSocket時進行使用者認證,確保只有已登入的使用者可以進入聊天室。
  2. 聊天室管理:建立多個聊天室,並允許使用者選擇加入不同的聊天室。
  3. 訊息儲存:將聊天記錄儲存到資料庫中,以便使用者在斷線重連後可以檢視歷史訊息。
  4. 訊息通知:實現訊息通知功能,當使用者收到新訊息時,透過瀏覽器通知或郵件提醒使用者。
  5. 實時線上使用者列表:顯示當前線上使用者列表,並在使用者進入或離開聊天室時實時更新。

實時地理位置共享

假設我們正在開發一個實時地理位置共享應用,使用者可以在地圖上實時看到其他使用者的位置。以下是一個簡單的示例程式碼:

後端程式碼

# consumers.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class LocationConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = 'location_room'
        self.room_group_name = f'location_{self.room_name}'

        # 加入地理位置共享房間
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # 離開地理位置共享房間
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        latitude = text_data_json['latitude']
        longitude = text_data_json['longitude']

        # 傳送位置資訊到地理位置共享房間
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'location_message',
                'latitude': latitude,
                'longitude': longitude
            }
        )

    async def location_message(self, event):
        latitude = event['latitude']
        longitude = event['longitude']

        # 傳送位置資訊給WebSocket連線
        await self.send(text_data=json.dumps({
            'latitude': latitude,
            'longitude': longitude
        }))

前端程式碼

// location.js

const locationSocket = new WebSocket('ws://localhost:8000/ws/location/');

locationSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    const latitude = data['latitude'];
    const longitude = data['longitude'];
    // 在地圖上顯示使用者位置
    updateMap(latitude, longitude);
};

locationSocket.onclose = function(e) {
    console.error('Location socket closed unexpectedly');
};

function sendLocation(latitude, longitude) {
    locationSocket.send(JSON.stringify({
        'latitude': latitude,
        'longitude': longitude
    }));
}

在這個示例中,使用者透過前端介面在地圖上選擇或移動位置,然後透過WebSocket傳送位置資訊到伺服器。伺服器接收到位置資訊後,將其廣播給所有連線的使用者,前端介面接收到位置資訊後,在地圖上實時更新其他使用者的位置。

這樣的實時地理位置共享功能可以應用在社交應用、實時導航應用等場景中,為使用者提供更好的互動體驗。

實時資料視覺化

假設我們有一個資料監控系統,需要實時展示各種感測器的資料。以下是一個簡單的示例程式碼:

後端程式碼

# consumers.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class SensorDataConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = 'sensor_data_room'
        self.room_group_name = f'sensor_data_{self.room_name}'

        # 加入感測器資料共享房間
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # 離開感測器資料共享房間
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        sensor_id = text_data_json['sensor_id']
        sensor_value = text_data_json['sensor_value']

        # 傳送感測器資料到感測器資料共享房間
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'sensor_data_message',
                'sensor_id': sensor_id,
                'sensor_value': sensor_value
            }
        )

    async def sensor_data_message(self, event):
        sensor_id = event['sensor_id']
        sensor_value = event['sensor_value']

        # 傳送感測器資料給WebSocket連線
        await self.send(text_data=json.dumps({
            'sensor_id': sensor_id,
            'sensor_value': sensor_value
        }))

前端程式碼

// sensor_data.js

const sensorDataSocket = new WebSocket('ws://localhost:8000/ws/sensor_data/');

sensorDataSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    const sensorId = data['sensor_id'];
    const sensorValue = data['sensor_value'];
    // 更新感測器資料圖表
    updateChart(sensorId, sensorValue);
};

sensorDataSocket.onclose = function(e) {
    console.error('Sensor data socket closed unexpectedly');
};

function sendSensorData(sensorId, sensorValue) {
    sensorDataSocket.send(JSON.stringify({
        'sensor_id': sensorId,
        'sensor_value': sensorValue
    }));
}

在這個示例中,感測器裝置透過WebSocket將實時資料傳送到伺服器,伺服器接收到資料後將其廣播給所有連線的使用者,前端介面接收到資料後,使用JavaScript圖表庫將實時資料實時展示在圖表中。

這樣的實時資料視覺化功能可以應用在資料監控、實時分析等場景中,為使用者提供實時的資料展示和監控功能。

如何基於Django中的WebSockets和非同步檢視來實現實時通訊功能

高階功能和進階應用

除了基本的實時聊天功能之外,結合WebSockets和非同步檢視還可以實現一系列高階功能和進階應用。以下是一些示例:

1. 實時地理位置共享

利用WebSocket和非同步檢視,可以實現使用者之間實時的地理位置共享。當使用者移動時,前端應用可以將使用者的位置資訊傳送到伺服器,伺服器再將這些資訊廣播給其他使用者。這種功能在社交應用、地圖導航應用等場景中非常有用。

2. 實時資料視覺化

在資料分析和監控領域,實時資料視覺化是一項重要的任務。透過WebSocket和非同步檢視,可以實時將資料傳輸到前端,並利用JavaScript圖表庫(如Highcharts、Chart.js等)實時展示資料變化趨勢、實時監控系統狀態等。

3. 線上協作編輯

利用WebSocket和非同步檢視,可以實現多人線上協作編輯功能,類似於Google Docs。當一個使用者編輯文件時,其餘使用者可以實時看到編輯內容的變化,從而實現多人實時協作編輯。

4. 實時遊戲

實時遊戲對於實時通訊的需求非常高。結合WebSocket和非同步檢視,可以實現實時的多人線上遊戲,比如棋牌遊戲、實時戰略遊戲等,提供流暢的遊戲體驗。

5. 實時搜尋和過濾

在網站和應用中,使用者可能需要實時搜尋和過濾資料。透過WebSocket和非同步檢視,可以實時向伺服器傳送搜尋關鍵詞或過濾條件,並實時獲取伺服器返回的搜尋結果或過濾後的資料,從而提高使用者體驗。

6. 實時投票和問卷調查

線上投票和問卷調查通常需要實時獲取投票結果或問卷填寫情況。結合WebSocket和非同步檢視,可以實時更新投票結果圖表或問卷統計資料,讓使用者實時瞭解當前的投票情況或問卷填寫進度。

總結

本文介紹瞭如何利用Django中的WebSockets和非同步檢視來實現實時通訊功能。我們首先了解了WebSockets的基本概念和工作原理,以及Django中使用django-channels庫來支援WebSockets的方法。接著,我們深入探討了非同步檢視的概念和用法,以及如何結合WebSockets和非同步檢視來實現實時通訊功能。

透過一個簡單的實時聊天應用的示例,我們演示瞭如何建立WebSocket消費者(Consumer)來處理WebSocket連線,並利用非同步檢視來處理WebSocket連線中的事件。我們還介紹瞭如何在前端頁面中使用JavaScript來連線WebSocket,並實時處理接收到的訊息。

隨後,我們進一步探討了結合WebSockets和非同步檢視的優勢,並提供了一系列高階功能和進階應用的示例,包括實時地理位置共享、實時資料視覺化等。這些功能和應用可以為開發者提供更多的創新和可能性,從而滿足不同場景下的實時通訊需求

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章