淺談Django實作websocket

websocket

眾所皆知Http協議是單向的由用戶端(Browser;Client)向服務端發送請求,再由服務端(Server)回覆,不過在當我們有些實務應用(例如最常見的即時聊天室、共同編輯、訊息推播…等應用),需要由Server主動向Client發送資訊該怎麼辦呢?

所以本次將介紹如何在Django透過Channels啟動Socekt功能,達到雙向互動.

一、準備工作

我想怎麼安裝Django以及啟動就不特別說明,不過這次要用到的套件為Channels記得安裝.

pip install channels==3.0.4 #T編使用4.x有些問題,這邊就先採用3.0.4版本

二、設定相關資訊

1.在專案settings.py中加入Channels

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "channels",
]

2.同樣在settings.py加入ASGI,主要是原生Django採用WSGI,當要使用websocket會採用異步方式,所要啟用ASGI

WSGI : Web Server Gateway Interface 網頁閘道器介面

ASGI: Asynchronous Server Gateway Interface 異步閘道器介面

Ref: 凡寫過必留下痕跡

ASGI_APPLICATION = "demosocket.asgi.application" #很重要一個點xxx.asgi.application中xxx是專案的名稱

3.此時會發現啟動Django的畫面不同了

WSGI

Django version 4.1.4, using settings 'demosocket.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

ASGI

Django version 4.1.4, using settings 'demosocket.settings'
Starting ASGI/Channels version 3.0.4 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

三、建立APP與畫面

1.建立app

django-admin startapp web

2.建立路由

urlpatterns = [
    path("web/", web.views.index),
]

3.建立基本頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .message{
            height:300px;
            border: 1px solid #dddddd;
            width: 100%;
        }
    </style>
</head>
<body>
<div class="message" id="message"></div>
<div>
    <input type="text" placeholder="請輸入" id="txt">
    <input type="button" value="送出" onclick="sendMessage()">
    <input type="button" value="關閉" onclick="closeConn()">
</div>

4.建立View

from django.shortcuts import render

def index(request):
	return render(request, 'index.html')

5.做好以上,啟動服務應該可以看到下面的畫面(連線成功字樣後續會提及)

測試畫面

四、建立Websocket與路由

1.在專案目錄下建立routings.py

from django.urls import re_path
from web.Consumers.consumers import ChatConsumer #稍後自行建立的consumers.py

websocket_urlpatterns =[
	re_path(r'chat/', ChatConsumer.as_asgi()),
]

3.在專案目錄下找到asgi.py修改為

from channels.routing import ProtocolTypeRouter, URLRouter
from demosocket import routings

application = ProtocolTypeRouter({
	"http": get_asgi_application(), 
        "websocket": URLRouter(routings.websocket_urlpatterns), 
})

4.建立consumers.py

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


CONN_LIST = [] #本次展示群發功能,不採用lays用法,所以需要裝載所有連接的客戶資訊
class ChatConsumer(WebsocketConsumer):

    def websocket_connect(self, message):
        #有客戶端來向後端發送ws自動觸發
        print('有人來連接')
        self.accept() #後端允許連結
        CONN_LIST.append(self)

    def websocket_receive(self, message):
        #瀏覽器基於ws傳送資料,自動觸發接受消息(message)
        print('接收瀏覽器的訊息-->', message['text'])
        text = message['text']
        res ="{}".format(text)
        for conn in CONN_LIST:
            conn.send(res)

        if text == 'close':
            self.send('你要求我關閉喔!')
            self.close() #後端自動斷開
            #raise StopConsumer()  # 後端不允許連結,就不會執行websocket_disconnect
            return

    def websocket_disconnect(self, message):
        #客戶端離線就觸發
        print('客戶端斷開')
        CONN_LIST.remove()
        raise StopConsumer() #後端不允許連結
 

五、準備進行測試

1.別急,我們還沒設定連結websocket功能,這邊只需要在剛剛index.html介面,透過Javascript進行觸發即可

...html資訊略
<script>
    var socket = new WebSocket("ws://127.0.0.1:8000/chat/");

    //回調函數,建立好連線後自動觸發(self.accept)
    socket.onopen = function(event){
        let tag = document.createElement('div');
        tag.innerText = '連線成功!';
        document.getElementById('message').appendChild(tag);
    }

    //回調函數,接收後端來的資訊自動觸發
    socket.onmessage = function(event){
        let tag = document.createElement('div');
        tag.innerText = event.data;
        document.getElementById('message').appendChild(tag);
    }

    //回調函數,後端主動斷開後會觸發
    socket.onclose = function(event){
        let tag = document.createElement('div');
        tag.innerText = '被後端斷開';
        document.getElementById('message').appendChild(tag);
    }

    //傳訊息
    function sendMessage(){
         let tag = document.getElementById('txt');
         socket.send(tag.value);
    }

    //關閉
    function closeConn(){
         socket.close();
    }

</script>

2.啟動服務進入頁面http://localhost:8000/web/,應該就會看到服務連接到的訊息

WebSocket連線成功

3.同時我們啟動另一個不同的瀏覽器,一樣網址,然後我們輸入一個訊息看看吧!

透過其他瀏覽器連上網頁
訊息可以互相傳遞
訊息可以互相傳遞

六、結語

以上就是最基本的WebSocket應用,這次介紹的是類似群發的功能,實務上的應用可能會有很多聊天群組,彼此是不會看到別的群組的訊息,這有機會T編在跟各位分享!

Loading

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *