中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

怎么在Django中利用Channels實現(xiàn)一個WebSocket功能-創(chuàng)新互聯(lián)

這篇文章給大家介紹怎么在Django中利用Channels實現(xiàn)一個WebSocket功能,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),定日企業(yè)網(wǎng)站建設(shè),定日品牌網(wǎng)站建設(shè),網(wǎng)站定制,定日網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,定日網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

WebSocket是什么?

WebSocket是一種在單個TCP連接上進行全雙工通訊的協(xié)議。WebSocket允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在WebSocket協(xié)議中,客戶端瀏覽器和服務(wù)器只需要完成一次握手就可以創(chuàng)建持久性的連接,并在瀏覽器和服務(wù)器之間進行雙向的數(shù)據(jù)傳輸。

WebSocket有什么用?

WebSocket區(qū)別于HTTP協(xié)議的一個最為顯著的特點是,WebSocket協(xié)議可以由服務(wù)端主動發(fā)起消息,對于瀏覽器需要及時接收數(shù)據(jù)變化的場景非常適合,例如在Django中遇到一些耗時較長的任務(wù)我們通常會使用Celery來異步執(zhí)行,那么瀏覽器如果想要獲取這個任務(wù)的執(zhí)行狀態(tài),在HTTP協(xié)議中只能通過輪訓的方式由瀏覽器不斷的發(fā)送請求給服務(wù)器來獲取最新狀態(tài),這樣發(fā)送很多無用的請求不僅浪費資源,還不夠優(yōu)雅,如果使用WebSokcet來實現(xiàn)就很完美了

WebSocket的另外一個應(yīng)用場景就是下文要說的聊天室,一個用戶(瀏覽器)發(fā)送的消息需要實時的讓其他用戶(瀏覽器)接收,這在HTTP協(xié)議下是很難實現(xiàn)的,但WebSocket基于長連接加上可以主動給瀏覽器發(fā)消息的特性處理起來就游刃有余了

初步了解WebSocket之后,我們看看如何在Django中實現(xiàn)WebSocket

Channels

Django本身不支持WebSocket,但可以通過集成Channels框架來實現(xiàn)WebSocket

Channels是針對Django項目的一個增強框架,可以使Django不僅支持HTTP協(xié)議,還能支持WebSocket,MQTT等多種協(xié)議,同時Channels還整合了Django的auth以及session系統(tǒng)方便進行用戶管理及認證。

我下文所有的代碼實現(xiàn)使用以下python和Django版本

python==3.6.3
django==2.2

集成Channels

我假設(shè)你已經(jīng)新建了一個django項目,項目名字就叫webapp,目錄結(jié)構(gòu)如下

project
 - webapp
  - __init__.py
  - settings.py
  - urls.py
  - wsgi.py
 - manage.py

1.  安裝channels

pip install channels==2.1.7

2.  修改settings.py文件,

# APPS中添加channels
INSTALLED_APPS = [
 'django.contrib.staticfiles',
 'channels',
]
# 指定ASGI的路由地址
ASGI_APPLICATION = 'webapp.routing.application'

channels運行于ASGI協(xié)議上,ASGI的全名是Asynchronous Server Gateway Interface。它是區(qū)別于Django使用的WSGI協(xié)議 的一種異步服務(wù)網(wǎng)關(guān)接口協(xié)議,正是因為它才實現(xiàn)了websocket

ASGI_APPLICATION 指定主路由的位置為webapp下的routing.py文件中的application

3.  setting.py的同級目錄下創(chuàng)建routing.py路由文件,routing.py類似于Django中的url.py指明websocket協(xié)議的路由

from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
 # 暫時為空,下文填充
})

4.  運行Django項目

C:\python36\python.exe D:/demo/tailf/manage.py runserver 0.0.0.0:80
Performing system checks...
Watching for file changes with StatReloader
System check identified no issues (0 silenced).
April 12, 2019 - 17:44:52
Django version 2.2, using settings 'webapp.settings'
Starting ASGI/Channels version 2.1.7 development server at http://0.0.0.0:80/
Quit the server with CTRL-BREAK.

仔細觀察上邊的輸出會發(fā)現(xiàn)Django啟動中的Starting development server已經(jīng)變成了Starting ASGI/Channels version 2.1.7 development server,這表明項目已經(jīng)由django使用的WSGI協(xié)議轉(zhuǎn)換為了Channels使用的ASGI協(xié)議

至此Django已經(jīng)基本集成了Channels框架

構(gòu)建聊天室

上邊雖然在項目中集成了Channels,但并沒有任何的應(yīng)用使用它,接下來我們以聊天室的例子來講解Channels的使用

假設(shè)你已經(jīng)創(chuàng)建好了一個叫chat的app,并添加到了settings.py的INSTALLED_APPS中,app的目錄結(jié)構(gòu)大概如下

chat
 - migrations
  - __init__.py
 - __init__.py
 - admin.py
 - apps.py
 - models.py
 - tests.py
 - views.py

我們構(gòu)建一個標準的Django聊天頁面,相關(guān)代碼如下

url:
from django.urls import path
from chat.views import chat
urlpatterns = [
 path('chat', chat, name='chat-url')
]
view:
from django.shortcuts import render
def chat(request):
 return render(request, 'chat/index.html')
template:
{% extends "base.html" %}
{% block content %}
 <textarea class="form-control" id="chat-log" disabled rows="20"></textarea><br/>
 <input class="form-control" id="chat-message-input" type="text"/><br/>
 <input class="btn btn-success btn-block" id="chat-message-submit" type="button" value="Send"/>
{% endblock %}

通過上邊的代碼一個簡單的web聊天頁面構(gòu)建完成了,訪問頁面大概樣子如下:

怎么在Django中利用Channels實現(xiàn)一個WebSocket功能

接下來我們利用Channels的WebSocket協(xié)議實現(xiàn)消息的發(fā)送接收功能

1.  先從路由入手,上邊我們已經(jīng)創(chuàng)建了routing.py路由文件,現(xiàn)在來填充里邊的內(nèi)容

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
application = ProtocolTypeRouter({
 'websocket': AuthMiddlewareStack(
  URLRouter(
   chat.routing.websocket_urlpatterns
  )
 ),
})

ProtocolTypeRouter: ASIG支持多種不同的協(xié)議,在這里可以指定特定協(xié)議的路由信息,我們只使用了websocket協(xié)議,這里只配置websocket即可

AuthMiddlewareStack: django的channels封裝了django的auth模塊,使用這個配置我們就可以在consumer中通過下邊的代碼獲取到用戶的信息

def connect(self):
 self.user = self.scope["user"]

self.scope類似于django中的request,包含了請求的type、path、header、cookie、session、user等等有用的信息

URLRouter: 指定路由文件的路徑,也可以直接將路由信息寫在這里,代碼中配置了路由文件的路徑,會去chat下的routeing.py文件中查找websocket_urlpatterns,chat/routing.py內(nèi)容如下

from django.urls import path
from chat.consumers import ChatConsumer

websocket_urlpatterns = [
 path('ws/chat/', ChatConsumer),
]

routing.py路由文件跟django的url.py功能類似,語法也一樣,意思就是訪問ws/chat/都交給ChatConsumer處理

2.  接著編寫consumer,consumer類似django中的view,內(nèi)容如下

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
  }))

這里是個最簡單的同步websocket consumer類,connect方法在連接建立時觸發(fā),disconnect在連接關(guān)閉時觸發(fā),receive方法會在收到消息后觸發(fā)。整個ChatConsumer類會將所有收到的消息加上“運維咖啡吧:”的前綴發(fā)送給客戶端

3.  最后我們在html模板頁面添加websocket支持

{% extends "base.html" %}
{% block content %}
 <textarea class="form-control" id="chat-log" disabled rows="20"></textarea><br/>
 <input class="form-control" id="chat-message-input" type="text"/><br/>
 <input class="btn btn-success btn-block" id="chat-message-submit" type="button" value="Send"/>
{% endblock %}
{% block js %}
<script>
 var chatSocket = new WebSocket(
 'ws://' + window.location.host + '/ws/chat/');
 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>
{% endblock %}

WebSocket對象一個支持四個消息:onopen,onmessage,oncluse和onerror,我們這里用了兩個onmessage和onclose

onopen: 當瀏覽器和websocket服務(wù)端連接成功后會觸發(fā)onopen消息

onerror: 如果連接失敗,或者發(fā)送、接收數(shù)據(jù)失敗,或者數(shù)據(jù)處理出錯都會觸發(fā)onerror消息

onmessage: 當瀏覽器接收到websocket服務(wù)器發(fā)送過來的數(shù)據(jù)時,就會觸發(fā)onmessage消息,參數(shù)e包含了服務(wù)端發(fā)送過來的數(shù)據(jù)

onclose: 當瀏覽器接收到websocket服務(wù)器發(fā)送過來的關(guān)閉連接請求時,會觸發(fā)onclose消息

4.  完成前邊的代碼,一個可以聊天的websocket頁面就完成了,運行項目,在瀏覽器中輸入消息就會通過websocket-->rouging.py-->consumer.py處理后返回給前端

怎么在Django中利用Channels實現(xiàn)一個WebSocket功能

啟用Channel Layer

上邊的例子我們已經(jīng)實現(xiàn)了消息的發(fā)送和接收,但既然是聊天室,肯定要支持多人同時聊天的,當我們打開多個瀏覽器分別輸入消息后發(fā)現(xiàn)只有自己收到消息,其他瀏覽器端收不到,如何解決這個問題,讓所有客戶端都能一起聊天呢?

Channels引入了一個layer的概念,channel layer是一種通信系統(tǒng),允許多個consumer實例之間互相通信,以及與外部Djanbo程序?qū)崿F(xiàn)互通。

channel layer主要實現(xiàn)了兩種概念抽象:

channel name: channel實際上就是一個發(fā)送消息的通道,每個Channel都有一個名稱,每一個擁有這個名稱的人都可以往Channel里邊發(fā)送消息

group: 多個channel可以組成一個Group,每個Group都有一個名稱,每一個擁有這個名稱的人都可以往Group里添加/刪除Channel,也可以往Group里發(fā)送消息,Group內(nèi)的所有channel都可以收到,但是無法發(fā)送給Group內(nèi)的具體某個Channel

了解了上邊的概念,接下來我們利用channel layer實現(xiàn)真正的聊天室,能夠讓多個客戶端發(fā)送的消息被彼此看到

1.  官方推薦使用redis作為channel layer,所以先安裝channels_redis

pip install channels_redis==2.3.3

2.  然后修改settings.py添加對layer的支持

CHANNEL_LAYERS = {
 'default': {
  'BACKEND': 'channels_redis.core.RedisChannelLayer',
  'CONFIG': {
   "hosts": [('ops-coffee.cn', 6379)],
  },
 },
}

添加channel之后我們可以通過以下命令檢查通道層是否能夠正常工作

>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>>
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel',{'site':'https://ops-coffee.cn'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'site': 'https://ops-coffee.cn'}
>>>

3.  consumer做如下修改引入channel layer

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json
class ChatConsumer(WebsocketConsumer):
 def connect(self):
  self.room_group_name = 'ops_coffee'
  # 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
  }))

這里我們設(shè)置了一個固定的房間名作為Group name,所有的消息都會發(fā)送到這個Group里邊,當然你也可以通過參數(shù)的方式將房間名傳進來作為Group name,從而建立多個Group,這樣可以實現(xiàn)僅同房間內(nèi)的消息互通

當我們啟用了channel layer之后,所有與consumer之間的通信將會變成異步的,所以必須使用async_to_sync

一個鏈接(channel)創(chuàng)建時,通過group_add將channel添加到Group中,鏈接關(guān)閉通過group_discard將channel從Group中剔除,收到消息時可以調(diào)用group_send方法將消息發(fā)送到Group,這個Group內(nèi)所有的channel都可以收的到

group_send中的type指定了消息處理的函數(shù),這里會將消息轉(zhuǎn)給chat_message函數(shù)去處理

4.  經(jīng)過以上的修改,我們再次在多個瀏覽器上打開聊天頁面輸入消息,發(fā)現(xiàn)彼此已經(jīng)能夠看到了,至此一個完整的聊天室已經(jīng)基本完成

修改為異步

我們前邊實現(xiàn)的consumer是同步的,為了能有更好的性能,官方支持異步的寫法,只需要修改consumer.py即可

from channels.generic.websocket import AsyncWebsocketConsumer
import json
class ChatConsumer(AsyncWebsocketConsumer):
 async def connect(self):
  self.room_group_name = 'ops_coffee'
  # Join room group
  await self.channel_layer.group_add(
   self.room_group_name,
   self.channel_name
  )
  await self.accept()
 async def disconnect(self, close_code):
  # Leave room group
  await self.channel_layer.group_discard(
   self.room_group_name,
   self.channel_name
  )
 # Receive message from WebSocket
 async def receive(self, text_data):
  text_data_json = json.loads(text_data)
  message = text_data_json['message']
  # Send message to room group
  await self.channel_layer.group_send(
   self.room_group_name,
   {
    'type': 'chat_message',
    'message': message
   }
  )
 # Receive message from room group
 async def chat_message(self, event):
  message = '運維咖啡吧:' + event['message']
  # Send message to WebSocket
  await self.send(text_data=json.dumps({
   'message': message
  }))

其實異步的代碼跟之前的差別不大,只有幾個小區(qū)別:

ChatConsumer由WebsocketConsumer修改為了AsyncWebsocketConsumer

所有的方法都修改為了異步defasync def

用await來實現(xiàn)異步I/O的調(diào)用

channel layer也不再需要使用async_to_sync了

關(guān)于怎么在Django中利用Channels實現(xiàn)一個WebSocket功能就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

當前標題:怎么在Django中利用Channels實現(xiàn)一個WebSocket功能-創(chuàng)新互聯(lián)
URL分享:http://www.rwnh.cn/article28/cegccp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、動態(tài)網(wǎng)站、網(wǎng)站維護、App設(shè)計Google、網(wǎng)站設(shè)計公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

手機網(wǎng)站建設(shè)
江达县| 天气| 洱源县| 屯门区| 开江县| 龙口市| 梧州市| 高要市| 新余市| 泗阳县| 合作市| 陇川县| 海原县| 呈贡县| 瑞昌市| 建水县| 永城市| 双流县| 太和县| 内江市| 丹寨县| 义马市| 武平县| 安溪县| 集安市| 泰兴市| 九寨沟县| 屏南县| 宝兴县| 昌邑市| 昌平区| 日照市| 江源县| 天等县| 长岛县| 甘德县| 武定县| 英德市| 贵阳市| 浏阳市| 临夏县|