基于django channel實(shí)現(xiàn)websocket的聊天室的方法示例
websocket
- 網(wǎng)易聊天室?
- web微信?
- 直播?
假如你工作以后,你的老板讓你來(lái)開(kāi)發(fā)一個(gè)內(nèi)部的微信程序,你需要怎么辦?我們先來(lái)分析一下里面的技術(shù)難點(diǎn)
- 消息的實(shí)時(shí)性?
- 實(shí)現(xiàn)群聊
現(xiàn)在有這樣一個(gè)需求,老板給到你了,關(guān)乎你是否能轉(zhuǎn)正?你要怎么做?
我們先說(shuō)消息的實(shí)時(shí)性,按照我們目前的想法是我需要用http協(xié)議來(lái)做,那么http協(xié)議怎么來(lái)做那?
是不是要一直去訪問(wèn)我們的服務(wù)器,問(wèn)服務(wù)器有沒(méi)有人給我發(fā)消息,有沒(méi)有人給我發(fā)消息?那么大家認(rèn)為我多長(zhǎng)時(shí)間去訪問(wèn)一次服務(wù)比較合適那? 1分鐘1次?1分鐘60次?那這樣是不是有點(diǎn)問(wèn)題那?咱們都知道http發(fā)起一次請(qǐng)求就需要三次握手,四次斷開(kāi),那么這樣是不是對(duì)我服務(wù)器資源是嚴(yán)重的浪費(fèi)啊?對(duì)我本地的資源是不是也是嚴(yán)重的浪費(fèi)啊?這種方式咱們是不是一直去服務(wù)器問(wèn)啊?問(wèn)有沒(méi)有我的信息?有我就顯示?這種方式咱們一般稱為輪詢
http協(xié)議:
一次請(qǐng)求 一次相應(yīng) 斷開(kāi)
無(wú)狀態(tài)的 - 你曾經(jīng)來(lái)過(guò) session or cookie
在斷開(kāi)的情況下如果有數(shù)據(jù)只能等下次再訪問(wèn)的時(shí)候返回
那么我們先來(lái)總結(jié)一下,輪詢優(yōu)缺點(diǎn)
輪詢02年之前使用的都是這種技術(shù)
每分鐘訪問(wèn)60次服務(wù)器
優(yōu)點(diǎn):消息就基本實(shí)時(shí)
缺點(diǎn):雙資源浪費(fèi)
長(zhǎng)輪詢2000-現(xiàn)在一直在使用
客戶端發(fā)送一個(gè)請(qǐng)求- 服務(wù)器接受請(qǐng)求-不返回- 阻塞等待客戶端-如果有消息了-返回給客戶端
然后客戶端立即請(qǐng)求服務(wù)器
優(yōu)點(diǎn):節(jié)省了部分資源,數(shù)據(jù)實(shí)時(shí)性略差
缺點(diǎn):斷開(kāi)連接次數(shù)過(guò)多
那有沒(méi)有一種方法是:我的服務(wù)器知道我的客戶端在哪?有客戶端的消息的時(shí)候我就把數(shù)據(jù)發(fā)給客戶端
websocket是一種基于tcp的新網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器和服務(wù)器之間的雙全工通信,允許服務(wù)端直接向客戶端發(fā)送數(shù)據(jù)
websocket 是一個(gè)長(zhǎng)連接
現(xiàn)在咱們的前端已經(jīng)支持websocket協(xié)議了,可以直接使用websocket
簡(jiǎn)單應(yīng)用
<body> <!-- 輸入內(nèi)容--> <input type="text" id="input"> <!-- 提交數(shù)據(jù)--> <button> 提交數(shù)據(jù)</button> <!-- 顯示內(nèi)容--> <div> <div ></div> </div> <script> var input=document.getElementById('input'); var button=document.querySelector('button'); var message=document.querySelector('div'); //websocket在瀏覽器端如何使用 //現(xiàn)在html已經(jīng)提供了websocket,我們可以直接使用 var socket= new WebSocket('ws://echo.websocket.org'); socket.onopen=function () { message.innerHTML='連接成功了' }; //socket.addEventListener('open',function (data) { // message.innerHTML='連接成功了' //}); //點(diǎn)擊事件 button.onclick=function () { request=input.value; socket.send(request) } //獲取返回?cái)?shù)據(jù) socket.onmessage=function (data) { message.innerHTML=data.data }; socket.onclose=function (data) { message.innerHTML=data.data } </script> </body>
優(yōu)化前端代碼
button.onclick=function () { request=input.value; socket.send(request); input.value='' } //獲取返回?cái)?shù)據(jù) socket.onmessage = function (data) { var dv=document.createElement('div'); dv.innerHTML=data.data; message.appendChild(dv) };
websocket 事件
事件 | 事件處理函數(shù) | 描述 |
---|---|---|
open | socket.onopen | 連接建立是觸發(fā) |
message | socket.onmessage | 客戶端收到服務(wù)端數(shù)據(jù)是觸發(fā) |
error | socket.error | 通信發(fā)生錯(cuò)誤時(shí)觸發(fā) |
close | socket.close | 連接關(guān)閉時(shí)觸發(fā) |
websocket方法
方法 | 描述 |
---|---|
socket.send() | 使用連接發(fā)送數(shù)據(jù) |
socket.close() | 關(guān)閉連接 |
websocke treadyState值的狀態(tài)
值 | 描述 |
---|---|
0 (CONNECTING) | 正在鏈接中 |
1 (OPEN) | 已經(jīng)鏈接并且可以通訊 |
2 (CLOSING) | 連接正在關(guān)閉 |
3 (CLOSED) | 連接已關(guān)閉或者沒(méi)有鏈接成功 |
自建websocket服務(wù)端
準(zhǔn)備前端頁(yè)面
<!-- chat/templates/chat/index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Chat Rooms</title> </head> <body> What chat room would you like to enter?<br/> <input id="room-name-input" type="text" size="100"/><br/> <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 = '/web/' + roomName + '/'; }; </script> </body> </html>
編輯django的views,使其返回?cái)?shù)據(jù)
# chat/views.py from django.shortcuts import render def index(request): return render(request, 'chat/index.html', {})
修改url
from django.conf.urls import url from .views import * urlpatterns = [ url(r'^$', index, name='index'), ]
跟settings同級(jí)目錄下創(chuàng)建routing.py 文件
# mysite/routing.py from channels.routing import ProtocolTypeRouter application = ProtocolTypeRouter({ # (http->django views is added by default) })
編輯settings文件,將channels添加到installed_apps里面
INSTALLED_APPS = [ 'channels', 'chat', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
并添加channel的配置信息
ASGI_APPLICATION = 'mysite.routing.application'
準(zhǔn)備聊天室的頁(yè)面
<!-- chat/templates/chat/room.html --> <!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"/> </body> <script> var roomName = {{ room_name_json|safe }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); }; 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(); } }; document.querySelector('#chat-message-submit').onclick = function(e) { var messageInputDom = document.querySelector('#chat-message-input'); var message = messageInputDom.value; chatSocket.send(JSON.stringify({ 'message': message })); messageInputDom.value = ''; }; </script> </html>
準(zhǔn)備views文件,使其返回頁(yè)面
def room(request, room_name): return render(request, 'chat/room.html', { 'room_name_json':json.dumps(room_name) })
修改url
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'), ]
實(shí)現(xiàn)簡(jiǎn)單的發(fā)送返回
from channels.generic.websocket import WebsocketConsumer import json class ChatConsumer(WebsocketConsumer): def connect(self): self.accept() def disconnect(self, close_code): pass def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] self.send(text_data=json.dumps({ 'message': message }))
創(chuàng)建ws的路由
# chat/routing.py from django.conf.urls import url from . import consumers websocket_urlpatterns = [ url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer), ]
修改application的信息
# mysite/routing.py from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import chat.routing application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns ) ), })
執(zhí)行數(shù)據(jù)庫(kù)的遷移命令
python manage.py migrate
要實(shí)現(xiàn)群聊功能,還需要準(zhǔn)備redis
docker run -p 6379:6379 -d redis:2.8 pip3 install channels_redis
將redis添加到settings的配置文件中
# mysite/settings.py # Channels ASGI_APPLICATION = 'mysite.routing.application' CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, }
修改consumer.py文件
from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer import json class ChatConsumer(WebsocketConsumer): def connect(self): self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_group_name = 'chat_%s' % self.room_name # Join room group async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) self.accept() def disconnect(self, close_code): # Leave room group async_to_sync(self.channel_layer.group_discard)( self.room_group_name, self.channel_name ) # Receive message from WebSocket def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] # Send message to room group async_to_sync(self.channel_layer.group_send)( self.room_group_name, { 'type': 'chat_message', 'message': message } ) # Receive message from room group def chat_message(self, event): message = event['message'] # Send message to WebSocket self.send(text_data=json.dumps({ 'message': message }))
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Tensorflow中批量讀取數(shù)據(jù)的案列分析及TFRecord文件的打包與讀取
這篇文章主要介紹了Tensorflow中批量讀取數(shù)據(jù)的案列分析及TFRecord文件的打包與讀取,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06用Python實(shí)現(xiàn)二叉樹(shù)、二叉樹(shù)非遞歸遍歷及繪制的例子
今天小編就為大家分享一篇用Python實(shí)現(xiàn)二叉樹(shù)、二叉樹(shù)非遞歸遍歷及繪制的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08pydantic?resolve解決嵌套數(shù)據(jù)結(jié)構(gòu)生成痛點(diǎn)分析
這篇文章主要為大家介紹了pydantic?resolve解決嵌套數(shù)據(jù)結(jié)構(gòu)生成痛點(diǎn)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04人工智能學(xué)習(xí)Pytorch進(jìn)階操作教程
這篇文章主要為大家介紹了人工智能學(xué)習(xí)Pytorch進(jìn)階操作的詳解教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11Python中如何處理常見(jiàn)報(bào)錯(cuò)
大家好,本篇文章主要講的是Python中如何處理常見(jiàn)報(bào)錯(cuò),感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01解決pyinstaller打包exe可執(zhí)行文件后運(yùn)行找不到pandas或者XXX模塊
這篇文章主要介紹了解決pyinstaller打包exe可執(zhí)行文件后運(yùn)行找不到pandas或者XXX模塊問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Python爬蟲(chóng)beautifulsoup4常用的解析方法總結(jié)
今天小編就為大家分享一篇關(guān)于Python爬蟲(chóng)beautifulsoup4常用的解析方法總結(jié),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02