時間:2023-07-06 11:36:02 | 來源:網(wǎng)站運營
時間:2023-07-06 11:36:02 來源:網(wǎng)站運營
Django實戰(zhàn):channels + websocket四步打造個在線聊天室(附動圖):Channels是Django團(tuán)隊研發(fā)的一個給Django提供websocket支持的框架,它同時支持http和websocket多種協(xié)議。使用channels可以讓你的Django應(yīng)用擁有實時通訊和給用戶主動推送信息的功能。本文將教你如何使用channels + websocket打造個在線聊天室。一共只有四步,你可以輕松上手并學(xué)會。項目中大部分代碼是基于channels的官方文檔的,加入了些自己的理解,以便新手學(xué)習(xí)使用。 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è)置通道層的通信后臺 - 本地測試用 CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer" } }
注意 :本例為了簡化代碼,使用了InMemoryChannelLayer做通道層(channel_layer)的通信后臺,實際生產(chǎn)環(huán)境中應(yīng)該需要使用redis作為后臺。這時你還需要安裝redis和channels_redis,然后添加如下配置: # 生產(chǎn)環(huán)境中使用redis做后臺,安裝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加入到項目urls.py中去,這和常規(guī)Django項目無異。 # 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), ]
第二步 編寫聊天室頁面 # 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 })
接下來我們編寫兩個模板文件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> 請輸入聊天室名稱: <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)容如下所示。為了幫助你理解前后端是怎么實現(xiàn)websocket實時通信的,我給每行js代碼添加了注釋,這對于你理解前端如何發(fā)送websocket的請求,如果處理后端發(fā)過來的websocket消息至關(guān)重要。 <!DOCTYPE html><html><head> <meta charset="utf-8"/> <title>Chat Room</title></head><body> <textarea id="chat-log" cols="100" rows="20"></textarea><br> <input id="chat-message-input" type="text" size="100"><br> <input id="chat-message-submit" type="button" value="Send"> {{ room_name|json_script:"room-name" }} <script> // 獲取房間名 const roomName = JSON.parse(document.getElementById('room-name').textContent); // 根據(jù)roomName拼接websocket請求地址,建立長連接 // 請求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連接時觸發(fā)此方法,展示歡迎提示 chatSocket.onopen = function(e) { document.querySelector('#chat-log').value += ('[公告]歡迎來到' + roomName + '討論群。請文明發(fā)言!/n') } // 從后臺接收到數(shù)據(jù)時觸發(fā)此方法 // 接收到后臺數(shù)據(jù)后對其解析,并加入到聊天記錄chat-log chatSocket.onmessage = function(e) { const data = JSON.parse(e.data); document.querySelector('#chat-log').value += (data.message + '/n'); }; // websocket連接斷開時觸發(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)點擊發(fā)送消息按鈕,通過websocket的send方法向后臺發(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></body></html>
此時如果你使用python manage.py runserver命令啟動測試服務(wù)器,當(dāng)你訪問一個名為/hello/的房間時,你將看到如下頁面: # 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路由時,推薦使用常見的路徑前綴 (如/ws) 來區(qū)分 WebSocket 連接與普通 HTTP 連接, 因為它將使生產(chǎn)環(huán)境中部署 Channels 更容易,比如nginx把所有/ws的請求轉(zhuǎn)給channels處理。 # 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請求使用這個 "http": get_asgi_application(), # websocket請求使用這個 "websocket": AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns ) ), })
在這里,channels的ProtocolTypeRouter會根據(jù)請求協(xié)議的類型來轉(zhuǎn)發(fā)請求。AuthMiddlewareStack將使用對當(dāng)前經(jīng)過身份驗證的用戶的引用來填充連接的scope, 類似于 Django 的request對象,我們后面還會講到。 import json from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer import datetime class ChatConsumer(WebsocketConsumer): # websocket建立連接時執(zhí)行方法 def connect(self): # 從url里獲取聊天室名字,為每個房間建立一個頻道組 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請求 self.accept() # websocket斷開時執(zhí)行方法 def disconnect(self, close_code): async_to_sync(self.channel_layer.group_discard)( self.room_group_name, self.channel_name ) # 從websocket接收到消息時執(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') # 通過websocket發(fā)送消息到客戶端 self.send(text_data=json.dumps({ 'message': f'{datetime_str}:{message}' }))
每個自定義的Consumer類一般繼承同步的WebsocketConsumer類或異步的AysncWebSocketConsumer類,它自帶 self.channel_name 和self.channel_layer 屬性。前者是獨一無二的長連接頻道名,后者提供了 send(), group_send()和group_add() 3種方法, 可以給單個頻道或一個頻道組發(fā)信息,還可以將一個頻道加入到組。 python manage.py makemigrations python manage.py migrate python manage.py runserver
小結(jié)關(guān)鍵詞:實戰(zhàn),打造
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。