Django Channels 學習筆記


一.為什么要使用Channels

  在Django中,默認使用的是HTTP通信,不過這種通信方式有個很大的缺陷,就是不能很好的支持實時通信。如果硬是要使用HTTP做實時通信的話只能在客戶端進行輪詢了,不過這樣做的開銷太大了。
  因此,在1.9版本之后,Django實現了對Channels的支持,他所使用的是WebSocket通信,解決了實時通信的問題,而且在使用WebSocket進行通信的同時依舊能夠支持HTTP通信。

二.創建一個Django Channels樣例的完整流程

1. 首先下載相應插件

pip install channels
pip install asgi_redis

2. 創建一個新工程

django-admin.py startproject channels_example


工程目錄如下:
|-- channels_example
|    |--channels_example
|        |-- __init__.py
|        |-- settings.py
|        |-- urls.py
|        |-- wsgi.py
|    |-- manage.py

這里我們要在內層的channels_example目錄下創建幾個channels所需的必要文件。
包括:
routing.py
consumer.py
asgi.py

這幾個文件先放着,之后我會一一介紹。
新的目錄如下:
|-- channels_example
|    |--channels_example
|        |-- __init__.py
|        |-- settings.py
|        |-- urls.py
|        |-- wsgi.py
|        |-- routing.py
|        |-- consumer.py
|        |-- asgi.py
|    |-- manage.py

3. 設置settings.py中的參數

這里首先將channels加入到INSTALLED_APPS中,如下:

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'channels', ]

然后,添加新的參數CHANNEL_LAYERS,如下:

CHANNEL_LAYERS = { "default": { "BACKEND": "asgiref.inmemory.ChannelLayer", "ROUTING": "channels_example.routing.channel_routing", }, }

這里,需要注意的是 ROUTING 參數,他是用來指定WebSocket表單的位置,當有WebSocket請求訪問時,就會根據這個路徑找到相應表單,調用相應的函數進行處理。
channels_example.routing 就是我們剛才建好的routing,py文件,里面的channel_routing我們下面會進行填充。

4. 填寫新的表單——routing.py文件

上一步我們已經了解到,routing.py文件其實就是個表單,功能和Django原來的urls.py文件一樣,不過這里使用的不是URL,而是請求的類型。

from channels.routing import route from channels_example import consumers  #導入處理函數
 channel_routing = [ #route("http.request", consumers.http_consumer), 這個表項比較特殊,他響應的是http.request,也就是說有HTTP請求時就會響應,同時urls.py里面的表單會失效
 route("websocket.connect", consumers.ws_connect),        #當WebSocket請求連接上時調用consumers.ws_connect函數
    route("websocket.receive", consumers.ws_message),        #當WebSocket請求發來消息時。。。
    route("websocket.disconnect", consumers.ws_disconnect),    #當WebSocket請求斷開連接時。。。
]

5. 填寫新的視圖文件——consumers.py

上一步我們已經了解到,routing.py相當於新的urls.py,而consumers.py就相當於新的view.py。

代碼:

from django.http import HttpResponse from channels.handler import AsgiHandler #message.reply_channel 一個客戶端通道的對象 #message.reply_channel.send(chunk) 用來唯一返回這個客戶端

#一個管道大概會持續30s

#當連接上時,發回去一個connect字符串
def ws_connect(message): message.reply_channel.send({"connect"}) #將發來的信息原樣返回
def ws_message(message): message.reply_channel.send({ "text": message.content['text'], }) #斷開連接時發送一個disconnect字符串,當然,他已經收不到了
def ws_disconnect(message): message.reply_channel.send({"disconnect"})

6. asgi.py文件

類似於Django自己生成的wsgi.py文件,內容如下:

import os import channels.asgi os.environ.setdefault("DJANGO_SETTINGS_MODULE", "channels_example.settings")    #這里填的是你的配置文件settings.py的位置
channel_layer = channels.asgi.get_channel_layer()

7. 發布運行

python manage.py runserver 0.0.0.0:8000

這是一個測試用的JS代碼,能夠發送和接收WebSocket請求:

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title></title>
    
      <script type="text/javascript">
         function WebSocketTest() { if ("WebSocket" in window) { alert("您的瀏覽器支持 WebSocket!"); socket = new WebSocket("ws://" + "localhost:8000" + "/channels_example/"); socket.onmessage = function(e) { alert(e.data); } socket.onopen = function() { socket.send("hello world"); } // Call onopen directly if socket is already open
                if (socket.readyState == WebSocket.OPEN) socket.onopen(); } else { // 瀏覽器不支持 WebSocket
 alert("您的瀏覽器不支持 WebSocket!"); } } </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">運行 WebSocket</a>
      </div>
      
   </body>
</html>

 

三.進階 —— Group

 

四.進階 —— message詳解

 

五.遇到的問題

1. 后來的請求可能會打斷之前請求的執行

比如,我寫了下面三個函數響應connect,send和disconnect

@channel_session def ws_message(message): #送給全組人
    print message.channel_session['room'] Group("chat-%s" % message.channel_session['room']).send({ "text": message.content['text'], }) @channel_session def ws_connect(message): room = message.content['path'].strip("/") message.channel_session['room'] = room Group("chat-%s" % room).add(message.reply_channel) @channel_session def ws_disconnect(message): print message.channel_session['room'] Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)

 

客戶端我這樣寫:

socket.onopen = function() { socket.send("hello world"); }

 

這樣就會產生一個問題,當客戶端連接到服務端時,服務端執行ws_connect()函數,然后客戶端又send()了數據,服務端就會去調用ws_message()函數,而終止了ws_connect()函數的執行。

此時,我的ws_connect()函數還未對message.channel_session['room']進行賦值,而ws_message()函數就調用了他,所以會報錯。

然后我嘗試了斷開連接,運行正常,這就說明ws_connect()函數最終還是執行完了,不過是在ws_message()函數執行之后。

綜上,當服務端接收到新的請求時,可能會打斷上個請求的執行,這點需要特殊注意一下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM