最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Django使用channels + websocket打造在線聊天室

 更新時(shí)間:2021年05月20日 15:32:57   作者:大江狗  
本文將教你如何使用channels + websocket打造個(gè)在線聊天室。一共只有四步,你可以輕松上手并學(xué)會(huì)。項(xiàng)目中大部分代碼是基于channels的官方文檔的,加入了些自己的理解,以便新手學(xué)習(xí)使用。

Channels是Django團(tuán)隊(duì)研發(fā)的一個(gè)給Django提供websocket支持的框架,它同時(shí)支持http和websocket多種協(xié)議。使用channels可以讓你的Django應(yīng)用擁有實(shí)時(shí)通訊和給用戶主動(dòng)推送信息的功能。

演示效果如下所示:

什么是websocket?

WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是 Ajax 輪詢。輪詢是在特定的的時(shí)間間隔(如每1秒),由瀏覽器對(duì)服務(wù)器發(fā)出HTTP請(qǐng)求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。這種傳統(tǒng)的模式帶來(lái)很明顯的缺點(diǎn),即瀏覽器需要不斷的向服務(wù)器發(fā)出請(qǐng)求,然而HTTP請(qǐng)求可能包含較長(zhǎng)的頭部,其中真正有效的數(shù)據(jù)可能只是很小的一部分,顯然這樣會(huì)浪費(fèi)很多的帶寬等資源。Websocket能更好的節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊,早已成為一種非常流行必須掌握的技術(shù)。

第一步 準(zhǔn)備工作

首先在虛擬環(huán)境中安裝django和channels(本項(xiàng)目使用了最新版本,均為3.X版本), 新建一個(gè)名為myproject的項(xiàng)目,新建一個(gè)app名為chat。如果windows下安裝報(bào)錯(cuò),如何解決自己網(wǎng)上去找吧。

pip install django==3.2.3
pip install channels==3.0.3

修改settings.py, 將channels和chat加入到INSTALLED_APPS里,并添加相應(yīng)配置,如下所示:

 INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'channels', # channels應(yīng)用
       'chat',  
 ]
 
 # 設(shè)置ASGI應(yīng)用
 ASGI_APPLICATION = 'myproject.asgi.application'
 
 # 設(shè)置通道層的通信后臺(tái) - 本地測(cè)試用
 CHANNEL_LAYERS = {
     "default": {
         "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
 }

注意 :本例為了簡(jiǎn)化代碼,使用了InMemoryChannelLayer做通道層(channel_layer)的通信后臺(tái),實(shí)際生產(chǎn)環(huán)境中應(yīng)該需要使用redis作為后臺(tái)。這時(shí)你還需要安裝redis和channels_redis,然后添加如下配置:

 # 生產(chǎn)環(huán)境中使用redis做后臺(tái),安裝channels_redis
 CHANNEL_LAYERS = {
     "default": {
         "BACKEND": "channels_redis.core.RedisChannelLayer",
         "CONFIG": {
             "hosts": [("127.0.0.1", 6379)],
              #或"hosts": [os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/1')],
        },
    },
 }

最后將chat應(yīng)用的urls.py加入到項(xiàng)目urls.py中去,這和常規(guī)Django項(xiàng)目無(wú)異。

 # myproject/urls.py
 
 from django.conf.urls import include
 from django.urls import path
 from django.contrib import admin
 
 urlpatterns = [
     path('chat/', include('chat.urls')),
     path('admin/', admin.site.urls),
 ]

第二步 編寫聊天室頁(yè)面

我們需要利用django普通視圖函數(shù)編寫兩個(gè)頁(yè)面,一個(gè)用于展示首頁(yè)(index), 通過(guò)表單讓用戶輸入聊天室的名稱(room_name),然后跳轉(zhuǎn)到相應(yīng)聊天室頁(yè)面;一個(gè)頁(yè)面用于實(shí)時(shí)展示聊天信息記錄,并允許用戶發(fā)送信息。

這兩個(gè)頁(yè)面對(duì)應(yīng)的路由及視圖函數(shù)如下所示:

 # chat/urls.py
 from django.urls import path
 from . import views
 
 urlpatterns = [
     path('', views.index, name='index'),
     path('<str:room_name>/', views.room, name='room'),
 ]
 
 # chat/views.py
 from django.shortcuts import render
 
 def index(request):
     return render(request, 'chat/index.html', {})
 
 def room(request, room_name):
     return render(request, 'chat/room.html', {
         'room_name': room_name
    })

接下來(lái)我們編寫兩個(gè)模板文件index.html和room.html。它們的路徑位置如下所示:

 chat/
    __init__.py
    templates/
        chat/
            index.html
            room.html
    urls.py
    views.py

index.html內(nèi)容如下所示。它也基本不涉及websocket,就是讓用戶輸入聊天室后進(jìn)行跳轉(zhuǎn)。

 <!-- chat/templates/chat/index.html -->
 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="utf-8"/>
     <title>Chat Rooms</title>
 </head>
 <body>
    請(qǐng)輸入聊天室名稱:
     <input id="room-name-input" type="text" size="100">
     <input id="room-name-submit" type="button" value="Enter">
 
     <script>
         document.querySelector('#room-name-input').focus();
         document.querySelector('#room-name-input').onkeyup = function(e) {
             if (e.keyCode === 13) {  // enter, return
                 document.querySelector('#room-name-submit').click();
            }
        };
 
         document.querySelector('#room-name-submit').onclick = function(e) {
             var roomName = document.querySelector('#room-name-input').value;
             window.location.pathname = '/chat/' + roomName + '/';
        };
     </script>
 </body>
 </html>

room.html內(nèi)容如下所示。為了幫助你理解前后端是怎么實(shí)現(xiàn)websocket實(shí)時(shí)通信的,我給每行js代碼添加了注釋,這對(duì)于你理解前端如何發(fā)送websocket的請(qǐng)求,如果處理后端發(fā)過(guò)來(lái)的websocket消息至關(guān)重要。

   <script>
        // 獲取房間名
        const roomName = JSON.parse(document.getElementById('room-name').textContent);
 
        // 根據(jù)roomName拼接websocket請(qǐng)求地址,建立長(zhǎng)連接
        // 請(qǐng)求url地址為/ws/chat/<room_name>/
        const wss_protocol = (window.location.protocol == 'https:') ? 'wss://': 'ws://';
        const chatSocket = new WebSocket(
             wss_protocol + window.location.host + '/ws/chat/'  + roomName + '/'
            );
 
        // 建立websocket連接時(shí)觸發(fā)此方法,展示歡迎提示
        chatSocket.onopen = function(e) {
            document.querySelector('#chat-log').value += ('[公告]歡迎來(lái)到' + roomName + '討論群。請(qǐng)文明發(fā)言!\n')
        }
 
        // 從后臺(tái)接收到數(shù)據(jù)時(shí)觸發(fā)此方法
        // 接收到后臺(tái)數(shù)據(jù)后對(duì)其解析,并加入到聊天記錄chat-log
         chatSocket.onmessage = function(e) {
             const data = JSON.parse(e.data);
             document.querySelector('#chat-log').value += (data.message + '\n');
        };
 
         // websocket連接斷開時(shí)觸發(fā)此方法
         chatSocket.onclose = function(e) {
             console.error('Chat socket closed unexpectedly');
        };
         
         document.querySelector('#chat-message-input').focus();
         document.querySelector('#chat-message-input').onkeyup = function(e) {
             if (e.keyCode === 13) {  // enter, return
                 document.querySelector('#chat-message-submit').click();
            }
        };
         
         // 每當(dāng)點(diǎn)擊發(fā)送消息按鈕,通過(guò)websocket的send方法向后臺(tái)發(fā)送信息。
         document.querySelector('#chat-message-submit').onclick = function(e) {
             const messageInputDom = document.querySelector('#chat-message-input');
             const message = messageInputDom.value;
             
             //注意這里:先把文本數(shù)據(jù)轉(zhuǎn)成json格式,然后調(diào)用send方法發(fā)送。
             chatSocket.send(JSON.stringify({
                 'message': message
            }));
             messageInputDom.value = '';
        };
     </script>

此時(shí)如果你使用python manage.py runserver命令啟動(dòng)測(cè)試服務(wù)器,當(dāng)你訪問(wèn)一個(gè)名為/hello/的房間時(shí),你將看到如下頁(yè)面:

到這里你看不到任何聊天記錄,也不能發(fā)送任何消息,因?yàn)槲覀冞€沒(méi)有在后端編寫任何代碼用于處理前端發(fā)來(lái)的消息,并返回?cái)?shù)據(jù)。在終端你還會(huì)看到如下報(bào)錯(cuò),  說(shuō)Django只能處理http連接,不能處理websocket。

到目前為止,我們所寫的就是一個(gè)普通的django應(yīng)用,還沒(méi)有用到channels庫(kù)處理websocket請(qǐng)求。接下來(lái)我們就要正式開始使用channels了。

第三步 編寫后臺(tái)websocket路由及處理方法

當(dāng) Django 接受 HTTP 請(qǐng)求時(shí), 它會(huì)根據(jù)根 URLconf 以查找視圖函數(shù), 然后調(diào)用視圖函數(shù)來(lái)處理請(qǐng)求。同樣, 當(dāng) channels 接受 WebSocket 連接時(shí), 它也會(huì)根據(jù)根路由配置去查找相應(yīng)的處理方法。只不過(guò)channels的路由不在urls.py中配置,處理方法也不寫在views.py。在channels中,這兩個(gè)文件分別變成了routing.py和consumers.py。這樣的好處是不用和django的常規(guī)應(yīng)用混在一起。

  • routing.py:websocket路由文件,相當(dāng)于django的urls.py。它根據(jù)websocket請(qǐng)求的url地址觸發(fā)consumers.py里定義的方法。
  • consumers.py:相當(dāng)于django的視圖views.py,負(fù)責(zé)處理通過(guò)websocket路由轉(zhuǎn)發(fā)過(guò)來(lái)的請(qǐng)求和數(shù)據(jù)。

在chat應(yīng)用下新建routing.py, 添加如下代碼。它的作用是將發(fā)送至ws/chat/<room_name>/的websocket請(qǐng)求轉(zhuǎn)由ChatConsumer處理。

 # chat/routing.py
 from django.urls import re_path
 
 from . import consumers
 
 websocket_urlpatterns = [
     re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
 ]

注意:定義websocket路由時(shí),推薦使用常見(jiàn)的路徑前綴 (如/ws) 來(lái)區(qū)分 WebSocket 連接與普通 HTTP 連接, 因?yàn)樗鼘⑹股a(chǎn)環(huán)境中部署 Channels 更容易,比如nginx把所有/ws的請(qǐng)求轉(zhuǎn)給channels處理。

與Django類似,我們還需要把這個(gè)app的websocket路由加入到項(xiàng)目的根路由中去。編輯myproject/asgi.py, 添加如下代碼:

 # myproject/asgi.py
 import os
 
 from channels.auth import AuthMiddlewareStack
 from channels.routing import ProtocolTypeRouter, URLRouter
 from django.core.asgi import get_asgi_application
 import chat.routing
 
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
 
 application = ProtocolTypeRouter({
    # http請(qǐng)求使用這個(gè)
   "http": get_asgi_application(),
   
   # websocket請(qǐng)求使用這個(gè)
   "websocket": AuthMiddlewareStack(
         URLRouter(
             chat.routing.websocket_urlpatterns
        )
    ),
 })

在這里,channels的ProtocolTypeRouter會(huì)根據(jù)請(qǐng)求協(xié)議的類型來(lái)轉(zhuǎn)發(fā)請(qǐng)求。AuthMiddlewareStack將使用對(duì)當(dāng)前經(jīng)過(guò)身份驗(yàn)證的用戶的引用來(lái)填充連接的scope, 類似于 Django 的request對(duì)象,我們后面還會(huì)講到。

接下來(lái)在chat應(yīng)用下新建consumers.py, 添加如下代碼:

 import json
 from asgiref.sync import async_to_sync
 from channels.generic.websocket import WebsocketConsumer
 import datetime
 
 
 class ChatConsumer(WebsocketConsumer):
     # websocket建立連接時(shí)執(zhí)行方法
     def connect(self):
         # 從url里獲取聊天室名字,為每個(gè)房間建立一個(gè)頻道組
         self.room_name = self.scope['url_route']['kwargs']['room_name']
         self.room_group_name = 'chat_%s' % self.room_name
 
         # 將當(dāng)前頻道加入頻道組
         async_to_sync(self.channel_layer.group_add)(
             self.room_group_name,
             self.channel_name
        )
 
         # 接受所有websocket請(qǐng)求
         self.accept()
 
     # websocket斷開時(shí)執(zhí)行方法
     def disconnect(self, close_code):
         async_to_sync(self.channel_layer.group_discard)(
             self.room_group_name,
             self.channel_name
        )
 
     # 從websocket接收到消息時(shí)執(zhí)行函數(shù)
     def receive(self, text_data):
         text_data_json = json.loads(text_data)
         message = text_data_json['message']
 
         # 發(fā)送消息到頻道組,頻道組調(diào)用chat_message方法
         async_to_sync(self.channel_layer.group_send)(
             self.room_group_name,
            {
                 'type': 'chat_message',
                 'message': message
            }
        )
 
     # 從頻道組接收到消息后執(zhí)行方法
     def chat_message(self, event):
         message = event['message']
         datetime_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
 
         # 通過(guò)websocket發(fā)送消息到客戶端
         self.send(text_data=json.dumps({
             'message': f'{datetime_str}:{message}'
        }))

每個(gè)自定義的Consumer類一般繼承同步的WebsocketConsumer類或異步的AysncWebSocketConsumer類,它自帶 self.channel_name 和self.channel_layer 屬性。前者是獨(dú)一無(wú)二的長(zhǎng)連接頻道名,后者提供了 send(), group_send()和group_add() 3種方法, 可以給單個(gè)頻道或一個(gè)頻道組發(fā)信息,還可以將一個(gè)頻道加入到組。

每個(gè)頻道(channel)都有一個(gè)名字。擁有頻道名稱的任何人都可以向頻道發(fā)送消息。

一個(gè)組(group)有一個(gè)名字。具有組名稱的任何人都可以按名稱向組添加/刪除頻道,并向組中的所有頻道發(fā)送消息。

注意:雖然異步Consumer類性能更優(yōu),channels推薦使用同步consumer類 , 尤其是調(diào)用Django ORM或其他同步程序時(shí),以保持整個(gè)consumer在單個(gè)線程中并避免ORM查詢阻塞整個(gè)event。調(diào)用channel_layer提供的方法時(shí)需要用async_to_sync轉(zhuǎn)換一下。

除此以外,我們還使用了self.scope['url_route']['kwargs']['room_name']從路由中獲取了聊天室的房間名,在channels程序中,scope是個(gè)很重要的對(duì)象,類似于django的request對(duì)象,它代表了當(dāng)前websocket連接的所有信息。你可以通過(guò)scope['user']獲取當(dāng)前用戶對(duì)象,還可以通過(guò)scope['path']獲取當(dāng)前當(dāng)前請(qǐng)求路徑。

第四步 運(yùn)行看效果

如果不出意外,你現(xiàn)在的項(xiàng)目布局應(yīng)該如下所示:

連續(xù)運(yùn)行如下命令,就可以看到我們文初的效果啦。

 python manage.py makemigrations

 python manage.py migrate

 python manage.py runserver

小結(jié)

我們已經(jīng)使用django + channels 寫了個(gè)在線聊天小應(yīng)用了,現(xiàn)在來(lái)總結(jié)下我們所學(xué)的知識(shí)吧。

  • websocket屬于全雙工通訊的協(xié)議,可以在服務(wù)器和客戶端之間保持長(zhǎng)連接,實(shí)現(xiàn)雙向數(shù)據(jù)傳輸。
  • 前端創(chuàng)建websocket對(duì)象后可以通過(guò)onmessage監(jiān)聽(tīng)并處理后端返回的數(shù)據(jù),可以通過(guò)send方法向后端發(fā)送數(shù)據(jù)。
  • channels對(duì)應(yīng)websocket的路由和處理方法分別寫在routing.py和consumers.py文件里,相當(dāng)于django的urls.py和views.py。
  • 每個(gè)頻道(channel)都有一個(gè)名字,擁有頻道名稱的任何人都可以向頻道發(fā)送消息。一個(gè)組(group)有一個(gè)名字,可以包含多個(gè)頻道。
  • 每個(gè)自定義的Consumer類自帶 self.channel_name 和self.channel_layer 屬性。前者是獨(dú)一無(wú)二的頻道名,后者提供了 send(), group_send()和group_add() 3種方法。
  • 在channels程序中,scope是個(gè)很重要的對(duì)象,類似于django的request對(duì)象,它代表了當(dāng)前websocket連接的所有信息,比如scope['user'], scope['path']。

本文的知識(shí)你學(xué)會(huì)了嗎? 學(xué)到了就點(diǎn)個(gè)贊吧!下期我們將利用channels + celery + redis打造個(gè)聊天機(jī)器人,歡迎關(guān)注!

以上就是Django使用channels + websocket打造在線聊天室的詳細(xì)內(nèi)容,更多關(guān)于Django 在線聊天室的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python的Django框架下管理站點(diǎn)的基本方法

    Python的Django框架下管理站點(diǎn)的基本方法

    這篇文章主要介紹了Python的Django框架下管理站點(diǎn)的基本方法,需是Django站點(diǎn)部署的基礎(chǔ),要的朋友可以參考下
    2015-07-07
  • OpenCV實(shí)現(xiàn)灰度、高斯模糊、邊緣檢測(cè)的示例

    OpenCV實(shí)現(xiàn)灰度、高斯模糊、邊緣檢測(cè)的示例

    這篇文章主要介紹了OpenCV實(shí)現(xiàn)灰度、高斯模糊、邊緣檢測(cè)的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 使用matplotlib繪制并排柱狀圖的實(shí)戰(zhàn)案例

    使用matplotlib繪制并排柱狀圖的實(shí)戰(zhàn)案例

    堆積柱狀圖有堆積柱狀圖的好處,比如說(shuō)我們可以很方便地看到多分類總和的趨勢(shì),下面這篇文章主要給大家介紹了關(guān)于使用matplotlib繪制并排柱狀圖的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • 使用Python實(shí)現(xiàn)圖像有效壓縮的方法

    使用Python實(shí)現(xiàn)圖像有效壓縮的方法

    在數(shù)字時(shí)代,圖像作為信息傳遞的重要媒介,在網(wǎng)頁(yè)設(shè)計(jì)、移動(dòng)應(yīng)用和多媒體制作中扮演著不可或缺的角色,本文將詳細(xì)介紹如何使用Python,一個(gè)功能強(qiáng)大且易于學(xué)習(xí)的編程語(yǔ)言,來(lái)實(shí)現(xiàn)圖像的有效壓縮,感興趣的朋友可以參考下
    2024-03-03
  • python print()函數(shù)的end參數(shù)和sep參數(shù)的用法說(shuō)明

    python print()函數(shù)的end參數(shù)和sep參數(shù)的用法說(shuō)明

    這篇文章主要介紹了python print()函數(shù)的end參數(shù)和sep參數(shù)的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-05-05
  • 用 Python 連接 MySQL 的幾種方式詳解

    用 Python 連接 MySQL 的幾種方式詳解

    這篇文章主要介紹了用 Python 連接 MySQL 的幾種方式,大家可以根據(jù)實(shí)際情況選擇合理的連接方式,需要的朋友可以參考下
    2018-04-04
  • matplotlib畫混淆矩陣與正確率曲線的實(shí)例代碼

    matplotlib畫混淆矩陣與正確率曲線的實(shí)例代碼

    混淆矩陣也稱誤差矩陣,是表示精度評(píng)價(jià)的一種標(biāo)準(zhǔn)格式,下面這篇文章主要給大家介紹了關(guān)于matplotlib畫混淆矩陣與正確率曲線的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • Python封裝shell命令實(shí)例分析

    Python封裝shell命令實(shí)例分析

    這篇文章主要介紹了Python封裝shell命令,實(shí)例分析了Python將各種常用shell命令封裝進(jìn)一個(gè)類中以便調(diào)用的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-05-05
  • 如何使用Python對(duì)Excel表格進(jìn)行拼接合并

    如何使用Python對(duì)Excel表格進(jìn)行拼接合并

    這篇文章主要介紹了Python處理EXCEL基礎(chǔ)操作篇3用Python對(duì)Excel表格進(jìn)行拼接合并,文中提供了解決思路和部分實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2023-03-03
  • Python自動(dòng)登錄126郵箱的方法

    Python自動(dòng)登錄126郵箱的方法

    這篇文章主要介紹了Python自動(dòng)登錄126郵箱的方法,涉及Python針對(duì)郵箱操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07

最新評(píng)論

清新县| 石泉县| 阳原县| 遵化市| 闽侯县| 丁青县| 巫溪县| 佛学| 龙门县| 青州市| 宁阳县| 华安县| 顺昌县| 晋中市| 礼泉县| 陆良县| 广水市| 洪湖市| 洞头县| 墨玉县| 祁东县| 江源县| 桐梓县| 湖口县| 孟村| 青岛市| 新营市| 五莲县| 平安县| 吉隆县| 五指山市| 阳东县| 唐山市| 五华县| 芷江| 招远市| 保山市| 柏乡县| 宿州市| 宁南县| 华坪县|