Django使用Channels实现WebSocket

WebSocket是什么?

WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket允许服务端主动向客户端推送数据。在WebSocket协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输。

WebSocket有什么用?

WebSocket区别于HTTP协议的一个最为显著的特点是,WebSocket协议可以由服务端主动发起消息,对于浏览器需要及时接收数据变化的场景非常适合,例如在Django中遇到一些耗时较长的任务我们通常会使用Celery来异步执行,那么浏览器如果想要获取这个任务的执行状态,在HTTP协议中只能通过轮训的方式由浏览器不断的发送请求给服务器来获取最新状态,这样发送很多无用的请求不仅浪费资源,还不够优雅,如果使用WebSokcet来实现就很完美了

WebSocket的另外一个应用场景就是下文要说的聊天室,一个用户(浏览器)发送的消息需要实时的让其他用户(浏览器)接收,这在HTTP协议下是很难实现的,但WebSocket基于长连接加上可以主动给浏览器发消息的特性处理起来就游刃有余了

初步了解WebSocket之后,我们看看如何在Django中实现WebSocket

Channels

Django本身不支持WebSocket,但可以通过集成Channels框架来实现WebSocket

Channels是针对Django项目的一个增强框架,可以使Django不仅支持HTTP协议,还能支持WebSocket,MQTT等多种协议,同时Channels还整合了Django的auth以及session系统方便进行用户管理及认证。

我下文所有的代码实现使用以下python和Django版本

  • python==3.6.3

  • django==2.2

一、集成channels,实现websocket聊天室

  1.项目结构 webapp_channels_websocket

  Django使用Channels实现WebSocket

 

 

   2.安装channels

pip install channels==2.1.7

  3.修改settings.py文件

#注册app
INSTALLED_APPS = [ ...... 'channels', 'chat', ] # 指定ASGI的路由地址 ASGI_APPLICATION = 'webapp_channels_websocket.routing.application'
#指定redis [channel layer是一种通信系统,允许多个consumer实例之间互相通信,以及与外部Djanbo程序实现互通] CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, }

  4.编写路由文件webapp_channels_websocket/routing.py

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing

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

  5.编写路由文件chat/routing.py

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

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

  6.编写chat/consumer.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer   #异步,实现更好的性能

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # connect方法在连接建立时触发
        self.room_group_name = 'chat_test'

        # 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):
        # disconnect在连接关闭时触发
        # 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):
        # receive方法会在收到消息后触发
        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
        }))

  7.编写url  webapp_channels_websocket/urls.py

from django.contrib import admin
from django.urls import path
from chat import views as chat_views

urlpatterns = [
    path('chat/', chat_views.chat),
]

  8.编写视图函数 chat/views.py

from django.shortcuts import render

def chat(request):
    return render(request, 'index.html')

  9.编写前端文件templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
      <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"/>

        <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>
</body>
</html>

  10.运行

Django使用Channels实现WebSocket

 

   11.效果

Django使用Channels实现WebSocket

 

上一篇:ZABBIX对接飞书实现报警通知


下一篇:使用protobuf设计消息协议(C++asio网络库相关)