Django實現WebSocket消息推送和聊天室

WebSocket定義

維基百科百度百科,或者 谷歌百度

WebSocket原理

原理的文字描述還請自行百度或者谷歌,我以自己的看法總結一個簡單的原理圖,有不足或錯誤請指正。
在這裏插入圖片描述

WebSocket的用途

web一般的模式是客戶端發送請求給服務端,服務端給出響應,在一些需要服務端主動發送消息給客戶端時,一般採用客戶端輪循訪問服務端獲取信息,這種方式非常佔用資源,使用WebSocket可以讓客戶端(網頁)和服務端(網站後端)實現TCP鏈接,兩者都能主動發送信息給對方,方便實現服務端主動發送信息給客戶端,一般你看到這篇文章時,無非是想是想實現兩個功能:

  • 網頁實時消息推送
  • 網頁聊天室

下面就針對這兩個簡單功能實現個小Demo。

WebSocket的Django實現

準備

首先在Django中實現websocket有多種途徑,一般有:channels和dwebsocket,針對這兩種方式,推薦新手使用後者,流程相對簡單。本文先討論使用dwebsocket來實現,在此之前,首先需要安裝dwebsocket。

pip install dwebsocket

dwebsocket的官方介紹和相關文檔我這裏不在贅述了,詳見dwebsocket的Github官網,下文中只會對幾個常用的對象和方法進行介紹。dwebsocket的Github站。安裝之後呢,希望能新建一個Django項目和App,先實驗成功再遷移到自己的項目中

djangp-admin startproject wstest

進入到新建的項目文件夾wstest中

djangp-admin startapp app

wstest中新建templates文件夾存放html頁面。在setting中配置模板目錄,找到TEMPLATES,修改DIRS屬性。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

實現

實時消息推送

  1. 服務器

在app.view.py中,首先編寫方法進行頁面跳轉:

from django.shortcuts import render,HttpResponse #引入HttpResponse
from dwebsocket.decorators import accept_websocket #引入dwbsocket的accept_websocket裝飾器

def to_sendmsg(request):
    return render(request,'sendmsg.html')

def to_recmsg(request):
    return render(request,'recmsg.html')

服務器方法,監聽客戶端的websocket鏈接。

clients={} #創建客戶端列表,存儲所有在線客戶端

# 允許接受ws請求
@accept_websocket
def link(request):
    # 判斷是不是ws請求
    if request.is_websocket():
        userid=str(uuid.uuid1())
        # 判斷是否有客戶端發來消息,若有則進行處理,若發來“test”表示客戶端與服務器建立鏈接成功
        while True:
            message=request.websocket.wait()
            if not message:
                break
            else:
                print("客戶端鏈接成功:"+str(message, encoding = "utf-8"))
                #保存客戶端的ws對象,以便給客戶端發送消息,每個客戶端分配一個唯一標識
                clients[userid]=request.websocket


發送消息方法,通過post請求發送消息。

def send(request):
    # 獲取消息
    msg=request.POST.get("msg")
    # 獲取到當前所有在線客戶端,即clients
    # 遍歷給所有客戶端推送消息
    for client in clients:
        clients[client].send(msg.encode('utf-8'))
    return HttpResponse({"msg":"success"})

配置url。

from django.contrib import admin
from django.urls import path
import app.views as views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('link/', views.link),
    path('send/', views.send),
    path('to_sendmsg/', views.to_recmsg),
    path('to_recmsg/', views.to_sendmsg),
]
  1. 客戶端

客戶端部分由JS來實現,JS的window對象中包含一個WebSocket類型(如果不包含則說明瀏覽器不支持),可以根據是否有這個類型來判斷瀏覽器是否支持WebSocket,判斷代碼如下:

if ("WebSocket" in window) {
//如果返回True則表示支持,否則不支持
}

JS的WebSocket使用也很簡單,主要就是創建鏈接和監聽消息,如下代碼展示一個最基礎的WebSocket的例子:

var ws = new WebSocket("ws://javafeng.com");
//鏈接打開時執行
ws.onopen = function(evt) { 
  alert("鏈接成功");
  ws.send("text");
};
//接收到消息時執行
ws.onmessage = function(evt) {
  var received_msg = evt.data;
  alert("收到消息"+received_msg);
};
//鏈接關閉時執行
ws.onclose = function(evt) {
 alert("鏈接關閉");
};  

就下來就來根據以上展示的簡單案例,來編寫頁面的腳本。
templates中新建html頁面recmsg.html模擬用戶接收實時消息,新建sendmsg.html模擬管理員發送實時消息推送。

  • recmsg.html

既然是要接受消息,因此在頁面加載時就建立與服務端的鏈接,在recmsg.html中編寫js腳本,使用alert來顯示服務端的消息推送,使用js發送ws請求代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>實時消息接收</title>
</head>
<body>
<script>
    window.onload = function () {
        //先判斷瀏覽器是否支持websocket
        if ("WebSocket" in window) {
            alert("您的瀏覽器支持 WebSocket!");
            // 打開一個 web socket
            ws = new WebSocket("ws://127.0.0.1:8000/link/");
            ws.onopen = function () {
                // Web Socket 已連接上,使用 send() 方法嘗試發送數據
                ws.send("test");
            };
            //監聽服務端是否有消息發送過來,當有消息時執行方法
            ws.onmessage = function (evt) {
                //獲取服務器發來的消息
                var received_msg = evt.data;
                //顯示消息
                alert("收到消息:"+received_msg)
            };
            //關閉頁面或其他行爲導致與服務器斷開鏈接是執行
            ws.onclose = function () {
                // 關閉 websocket
                alert("連接已關閉...");
            };
        } else {
            // 瀏覽器不支持 WebSocket
            alert("您的瀏覽器不支持 WebSocket!");
        }
    }
</script>
</body>
</html>

其中在判斷是否鏈接成功時,還可以加代碼判斷ws.readyState的值,readyState屬性有四種取值:
CONNECTING:值爲0,表示正在連接。
OPEN:值爲1,表示連接成功,可以通信了。
CLOSING:值爲2,表示連接正在關閉。
CLOSED:值爲3,表示連接已經關閉,或者打開連接失敗。

  • sendmsg.html

記得引用jquery<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>,之後就很方便了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
</head>
<body>
    <input id="msg" placeholder="輸入要推送的消息">
    <button onclick="javascript:sendmsg()">推送</button>
<script>
    function sendmsg() {
        $.post("/send/",{msg:$("#msg").val()},function (result) {
            if(result.msg=="success"){
                alert("消息推送成功")
            }
        })
    }
</script>
</body>
</html>

接下來就是啓動項目,使用兩個客戶端,進行消息接收,啓動後,打開兩個瀏覽器窗口,均輸入http://127.0.0.1:8000/to_recmsg/,效果均如下:
在這裏插入圖片描述
這裏其實有一個坑,就是Django在2.1.5版本之後,會出現ws鏈接訪問400的錯誤,這裏建議使用Django2.1.4版本,後續研究到底是因爲什麼造成的400錯誤。

兩個窗口都關閉alert後,可以看到兩個客戶端都連接上了服務器:
在這裏插入圖片描述
這時打開第三個窗口,輸入http://127.0.0.1:8000/to_sendmsg/
在這裏插入圖片描述
輸入信息,點擊推送,回到客戶端的窗口可以看到推送的消息
在這裏插入圖片描述
這樣我們就完成了一個簡單的消息實時推送,後續的複雜功能大家就能按照自己的需求慢慢開發了。

聊天室

接下來是聊天室,可以做一對一或者羣組式的聊天室,首先分析下需求,設計頁面原型圖:
在這裏插入圖片描述
實現的邏輯順序和思路:

  • 加載頁面同時鏈接服務器
  • 服務器監聽客戶端鏈接,當有客戶端鏈接時,將其添加到在線列表,將在線列表和爲其分配的客戶端隨機id發送給當前鏈接的客戶端,並將列表發送給所有在線客戶端
  • 獲取服務端返回的隨機客戶端id和在線用戶列表
  • 加載用戶列表和本人id
  • 填寫消息,選擇羣組或者指定用戶,發送到後臺
  • 後臺服務器判斷是羣組消息還是私聊消息,根據不同情況發送給指定客戶端
  • 指定客戶端接收到消息,顯示在消息列表中
  • 離線時,服務器刪除制定用戶,更新在線列表,並將列表發送給所有在線客戶端

根據代碼編寫邏輯,整理了一個超級大圖,囊括了所有的流程以及對應代碼和頁面的內容,按照大圖可以更好地理解整個實現的流程在這裏插入圖片描述
照例先編寫後臺服務器部分,按照上述流程,和改造消息推送的代碼,實現起來並不難,比較繞的地方就是幾種消息類型的判斷不要弄混,其餘的代碼和消息推送的都很相似了。
聊天界面跳轉:

#聊天界面
def to_chat(request):
    return render(request,'chat.html')

服務器主要方法,負責處理客戶端在線列表,處理客戶端的請求,這裏沒有將消息發送的代碼寫在裏面,把發送的代碼單獨構建了一個方法。

# 服務器方法,允許接受ws請求
@accept_websocket
def chat(request):
    # 判斷是不是ws請求
    if request.is_websocket():
        # 保存客戶端的ws對象,以便給客戶端發送消息,每個客戶端分配一個唯一標識
        userid=str(uuid.uuid1())[:8]
        clients[userid] = request.websocket
        # 判斷是否有客戶端發來消息,若有則進行處理,表示客戶端與服務器建立鏈接成功
        while True:
            '''獲取消息,線程會阻塞,
            他會等待客戶端發來下一條消息,直到關閉後纔會返回,當關閉時返回None'''
            message=request.websocket.wait()
            if not message:
                break
            else:
                msg=str(message, encoding = "utf-8")
                print(msg)
                #1、發來test表示鏈接成功
                if msg == "test":
                    print("客戶端鏈接成功:"+userid)
                    #第一次進入,返回在線列表和他的id
                    request.websocket.send(json.dumps({"type":0,"userlist":list(clients.keys()),"userid":userid}).encode("'utf-8'"))
                    #更新所有人的userlist
                    for client in clients:
                        clients[client].send(json.dumps({"type":0,"userlist":list(clients.keys()),"user":None}).encode("'utf-8'"))
    #客戶端關閉後從列表刪除
    if userid in clients:
        del clients[userid]
        print(userid + "離線")
        # 更新所有人的userlist
        for client in clients:
            clients[client].send(
                json.dumps({"type": 0, "userlist": list(clients.keys()), "user": None}).encode("'utf-8'"))

接收到消息發送的請求之後,判斷是羣聊消息還是單聊消息,根據指定的類型發送給不同的目標用戶:

#消息發送方法
def msg_send(request):
    msg = request.POST.get("txt")
    useridto = request.POST.get("userto")
    useridfrom = request.POST.get("userfrom")
    type=request.POST.get("type")
    #發來{type:"2",msg:data,user:user},表示發送聊天信息,user爲空表示羣組消息,不爲空表示要發送至的用戶
    if type == "1":
        #羣發
        for client in clients:
            clients[client].send(json.dumps({"type": 1, "data": {"msg": msg, "user": useridfrom}}).encode('utf-8'))
    else:
        # 私聊,對方顯示
        clients[useridto].send(json.dumps({"type": 1, "data": {"msg": msg, "user": useridfrom}}).encode('utf-8'))
        # 私聊,自己顯示
        clients[useridfrom].send(json.dumps({"type": 1, "data": {"msg": msg, "user": useridfrom}}).encode('utf-8'))
    return HttpResponse(json.dumps({"msg":"success"}))

後臺就這麼簡單,就是消息推送的改造版。然後在url.py中添加如下幾條路由:

    # 聊天
    path('to_chat/', views.to_chat),
    path('chat/', views.chat),
    path('msg_send/', views.msg_send),

頁面設計比較簡單,我是用了最原始的表格框架,就簡單設計一下就好,JS腳本基本上也是通過消息推送部分進行了一些修改,整體理解結合大圖也很簡單。
編寫完後臺服務器之後,配置URL路由:

新建chat.html,代碼如下:

<!DOCTYPE html>
<html lang="zh">
<head>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        td {
            border: 1px #000 solid;
            margin: 0;
        }

        textarea {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body style="padding: 30px">
<span id="userid">我的隨機賬號:</span>
<table>
    <tr>
        <td style="width: 500px;">
            <div id="historymsg" style="height: 400px;overflow: auto"></div>
        </td>
        <td style="width: 400px">
            <div id="userlist" style="height: 400px;overflow: auto"></div>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <textarea id="msg"></textarea>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <select class="form-control" id="isgroup">
            </select>
            <input class="btn btn-info btn-block" type="button" onclick="send()" value="發送">
        </td>
    </tr>
</table>
<!--腳本開始-->

<!--腳本結束-->
</body>
</html>

頁面腳本

var ws,myid;
    window.onload = function () {
        //先判斷瀏覽器是否支持websocket
        if ("WebSocket" in window) {
            // 打開一個 web socket,鏈接服務器
            ws = new WebSocket("ws://127.0.0.1:8000/chat/");
            ws.onopen = function () {
                // Web Socket 已連接上,使用 send() 方法嘗試發送數據
                ws.send("test");
            };
            //監聽服務端是否有消息發送過來,當有消息時執行方法
            ws.onmessage = function (evt) {
                //獲取服務器發來的消息
                var received_msg = evt.data;
                //判斷是返回的是消息還是用戶列表和id,1是消息,0是用戶列表和id
                msg = eval("(" + received_msg + ")")
                //用戶列表和id
                if (msg.type == 0) {
                    //userid爲空表示更新用戶列表,不需要更新自己的id,否則爲初次登錄
                    if (msg.userid != null) {
                        myid = msg.userid
                        $("#userid").append(myid)
                    }
                    //當收到新的用戶列表時,清空原來的用戶列表,清空原來的用戶選擇框,添加羣組發送選項
                    $("#userlist").empty()
                    $("#isgroup").empty()
                    $("#isgroup").append("<option value='1'>羣發(或選擇要私聊的用戶)</option>")
                    for (var i = 0; i < msg.userlist.length; i++) {
                        //填充用戶列表
                        $("#userlist").append(msg.userlist[i] + "<hr />")
                        //填充用戶選擇框
                        $("#isgroup").append("<option value='" + msg.userlist[i] + "'>" + msg.userlist[i] + "</option>")
                    }
                }
                //用戶發送的消息
                else {
                    var myDate = new Date();
                    nowtime = myDate.toLocaleString(); //獲取日期與時間
                    newmsg = ""
                    //判斷是自己的消息,綠色顯示
                    if (myid == msg.data.user) {
                        newmsg = "<span style='color:blue'>" + msg.data.user + ":" + nowtime + "<br />" + msg.data.msg + "</span>" + "<br />"
                    } else {
                        newmsg = "<span >" + msg.data.user + ":" + nowtime + "<br />" + msg.data.msg + "</span>" + "<br />"
                    }
                    $("#historymsg").append(
                        newmsg
                    )
                }
            };
            //關閉頁面或其他行爲導致與服務器斷開鏈接是執行
            ws.onclose = function () {
                // 關閉 websocket
                alert("連接已關閉...");
            };
        } else {
            // 瀏覽器不支持 WebSocket
            alert("您的瀏覽器不支持 WebSocket!");
        }
    }
	//消息發送
    function send() {
        msgtxt = $("#msg").val()
        msguser = $("#isgroup").val()
        //判斷是否是羣發0是,不是0 則是私聊
        if ($("#isgroup").val() == "1") {
            msg = {
                type: "1",
                txt: msgtxt,
                userfrom: myid
            }
        } else {
            msg = {
                type: "0",
                txt: msgtxt,
                userto: msguser,
                userfrom: myid
            }
        }
        //發送消息後清空消息框,並定位到消息框內
        $.post("/msg_send/", msg, function () {
            $("#msg").val("")
            $("#msg").focus()
        })
    }

代碼編寫完成之後,運行項目,打開兩個窗口分別鏈接服務器,測試效果
在這裏插入圖片描述
這樣就實現了簡單的WebSocket聊天室,後續更多功能各位看官可以繼續開發,要是想深入瞭解更加深入的知識,可以出門右轉百度谷歌走一波,後續隨着使用也可能會除深入些的文章。
歡迎關注點贊評論,謝謝。有需要請email:[email protected]。代碼下載地址:https://download.csdn.net/download/u012751272/11108955,沒有分的朋友聯繫我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章