Django使用channels + websocket打造在线聊天室

Channels是Django团队研发的一个给Django提供websocket支持的框架,它同时支持http和websocket多种协议。使用channels可以让你的Django应用拥有实时通讯和给用户主动推送信息的功能。

演示效果如下所示:

什么是websocket?

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。Websocket能更好的节省服务器资源和带宽,并且能够更实时地进行通讯,早已成为一种非常流行必须掌握的技术。

第一步 准备工作

首先在虚拟环境中安装django和channels(本项目使用了最新版本,均为3.X版本), 新建一个名为myproject的项目,新建一个app名为chat。如果windows下安装报错,如何解决自己网上去找吧。

pip install django==3.2.3
pip install channels==3.0.3

修改settings.py, 将channels和chat加入到INSTALLED_APPS里,并添加相应配置,如下所示:

 INSTALLED_APPS = [
       'django.contrib.admin',
       'django.contrib.auth',
       'django.contrib.contenttypes',
       'django.contrib.sessions',
       'django.contrib.messages',
       'django.contrib.staticfiles',
       'channels', # channels应用
       'chat',  
 ]
 
 # 设置ASGI应用
 ASGI_APPLICATION = 'myproject.asgi.application'
 
 # 设置通道层的通信后台 - 本地测试用
 CHANNEL_LAYERS = {
     "default": {
         "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
 }

注意 :本例为了简化代码,使用了InMemoryChannelLayer做通道层(channel_layer)的通信后台,实际生产环境中应该需要使用redis作为后台。这时你还需要安装redis和channels_redis,然后添加如下配置:

 # 生产环境中使用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应用的urls.py加入到项目urls.py中去,这和常规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),
 ]

第二步 编写聊天室页面

我们需要利用django普通视图函数编写两个页面,一个用于展示首页(index), 通过表单让用户输入聊天室的名称(room_name),然后跳转到相应聊天室页面;一个页面用于实时展示聊天信息记录,并允许用户发送信息。

这两个页面对应的路由及视图函数如下所示:

 # 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内容如下所示。它也基本不涉及websocket,就是让用户输入聊天室后进行跳转。

 <!-- 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内容如下所示。为了帮助你理解前后端是怎么实现websocket实时通信的,我给每行js代码添加了注释,这对于你理解前端如何发送websocket的请求,如果处理后端发过来的websocket消息至关重要。

   <script>
        // 获取房间名
        const roomName = JSON.parse(document.getElementById('room-name').textContent);
 
        // 根据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连接时触发此方法,展示欢迎提示
        chatSocket.onopen = function(e) {
            document.querySelector('#chat-log').value += ('[公告]欢迎来到' + roomName + '讨论群。请文明发言!\n')
        }
 
        // 从后台接收到数据时触发此方法
        // 接收到后台数据后对其解析,并加入到聊天记录chat-log
         chatSocket.onmessage = function(e) {
             const data = JSON.parse(e.data);
             document.querySelector('#chat-log').value += (data.message + '\n');
        };
 
         // websocket连接断开时触发此方法
         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();
            }
        };
         
         // 每当点击发送消息按钮,通过websocket的send方法向后台发送信息。
         document.querySelector('#chat-message-submit').onclick = function(e) {
             const messageInputDom = document.querySelector('#chat-message-input');
             const message = messageInputDom.value;
             
             //注意这里:先把文本数据转成json格式,然后调用send方法发送。
             chatSocket.send(JSON.stringify({
                 'message': message
            }));
             messageInputDom.value = '';
        };
     </script>

此时如果你使用python manage.py runserver命令启动测试服务器,当你访问一个名为/hello/的房间时,你将看到如下页面:

到这里你看不到任何聊天记录,也不能发送任何消息,因为我们还没有在后端编写任何代码用于处理前端发来的消息,并返回数据。在终端你还会看到如下报错,  说Django只能处理http连接,不能处理websocket。

到目前为止,我们所写的就是一个普通的django应用,还没有用到channels库处理websocket请求。接下来我们就要正式开始使用channels了。

第三步 编写后台websocket路由及处理方法

当 Django 接受 HTTP 请求时, 它会根据根 URLconf 以查找视图函数, 然后调用视图函数来处理请求。同样, 当 channels 接受 WebSocket 连接时, 它也会根据根路由配置去查找相应的处理方法。只不过channels的路由不在urls.py中配置,处理方法也不写在views.py。在channels中,这两个文件分别变成了routing.py和consumers.py。这样的好处是不用和django的常规应用混在一起。

  • routing.py:websocket路由文件,相当于django的urls.py。它根据websocket请求的url地址触发consumers.py里定义的方法。
  • consumers.py:相当于django的视图views.py,负责处理通过websocket路由转发过来的请求和数据。

在chat应用下新建routing.py, 添加如下代码。它的作用是将发送至ws/chat/<room_name>/的websocket请求转由ChatConsumer处理。

 # 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) 来区分 WebSocket 连接与普通 HTTP 连接, 因为它将使生产环境中部署 Channels 更容易,比如nginx把所有/ws的请求转给channels处理。

与Django类似,我们还需要把这个app的websocket路由加入到项目的根路由中去。编辑myproject/asgi.py, 添加如下代码:

 # 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会根据请求协议的类型来转发请求。AuthMiddlewareStack将使用对当前经过身份验证的用户的引用来填充连接的scope, 类似于 Django 的request对象,我们后面还会讲到。

接下来在chat应用下新建consumers.py, 添加如下代码:

 import json
 from asgiref.sync import async_to_sync
 from channels.generic.websocket import WebsocketConsumer
 import datetime
 
 
 class ChatConsumer(WebsocketConsumer):
     # websocket建立连接时执行方法
     def connect(self):
         # 从url里获取聊天室名字,为每个房间建立一个频道组
         self.room_name = self.scope['url_route']['kwargs']['room_name']
         self.room_group_name = 'chat_%s' % self.room_name
 
         # 将当前频道加入频道组
         async_to_sync(self.channel_layer.group_add)(
             self.room_group_name,
             self.channel_name
        )
 
         # 接受所有websocket请求
         self.accept()
 
     # websocket断开时执行方法
     def disconnect(self, close_code):
         async_to_sync(self.channel_layer.group_discard)(
             self.room_group_name,
             self.channel_name
        )
 
     # 从websocket接收到消息时执行函数
     def receive(self, text_data):
         text_data_json = json.loads(text_data)
         message = text_data_json['message']
 
         # 发送消息到频道组,频道组调用chat_message方法
         async_to_sync(self.channel_layer.group_send)(
             self.room_group_name,
            {
                 'type': 'chat_message',
                 'message': message
            }
        )
 
     # 从频道组接收到消息后执行方法
     def chat_message(self, event):
         message = event['message']
         datetime_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
 
         # 通过websocket发送消息到客户端
         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种方法, 可以给单个频道或一个频道组发信息,还可以将一个频道加入到组。

每个频道(channel)都有一个名字。拥有频道名称的任何人都可以向频道发送消息。

一个组(group)有一个名字。具有组名称的任何人都可以按名称向组添加/删除频道,并向组中的所有频道发送消息。

注意:虽然异步Consumer类性能更优,channels推荐使用同步consumer类 , 尤其是调用Django ORM或其他同步程序时,以保持整个consumer在单个线程中并避免ORM查询阻塞整个event。调用channel_layer提供的方法时需要用async_to_sync转换一下。

除此以外,我们还使用了self.scope['url_route']['kwargs']['room_name']从路由中获取了聊天室的房间名,在channels程序中,scope是个很重要的对象,类似于django的request对象,它代表了当前websocket连接的所有信息。你可以通过scope['user']获取当前用户对象,还可以通过scope['path']获取当前当前请求路径。

第四步 运行看效果

如果不出意外,你现在的项目布局应该如下所示:

连续运行如下命令,就可以看到我们文初的效果啦。

 python manage.py makemigrations

 python manage.py migrate

 python manage.py runserver

小结

我们已经使用django + channels 写了个在线聊天小应用了,现在来总结下我们所学的知识吧。

  • websocket属于全双工通讯的协议,可以在服务器和客户端之间保持长连接,实现双向数据传输。
  • 前端创建websocket对象后可以通过onmessage监听并处理后端返回的数据,可以通过send方法向后端发送数据。
  • channels对应websocket的路由和处理方法分别写在routing.py和consumers.py文件里,相当于django的urls.py和views.py。
  • 每个频道(channel)都有一个名字,拥有频道名称的任何人都可以向频道发送消息。一个组(group)有一个名字,可以包含多个频道。
  • 每个自定义的Consumer类自带 self.channel_name 和self.channel_layer 属性。前者是独一无二的频道名,后者提供了 send(), group_send()和group_add() 3种方法。
  • 在channels程序中,scope是个很重要的对象,类似于django的request对象,它代表了当前websocket连接的所有信息,比如scope['user'], scope['path']。

本文的知识你学会了吗? 学到了就点个赞吧!下期我们将利用channels + celery + redis打造个聊天机器人,欢迎关注!

以上就是Django使用channels + websocket打造在线聊天室的详细内容,更多关于Django 在线聊天室的资料请关注我们其它相关文章!

(0)

相关推荐

  • Django Channels 实现点对点实时聊天和消息推送功能

    简介在很多实际的项目开发中,我们需要实现很多实时功能:而在这篇文章中,我们就利用django channels简单地实现了点对点聊天和消息推送功能. 手边有一个项目需要用到后台消息推送和用户之间一对一在线聊天的功能.例如用户A评论了用户B的帖子,这时候用户B就应该收到一条通知,显示自己的帖子被评论了.这个功能可以由最基本的刷新页面后访问数据库来完成,但是这样会增加对后台服务器的压力,同时如果是手机客户端的话,也会造成流量的损失.于是,我们考虑使用websocket建立一个连接来完成这个功能. 但

  • Python使用django框架实现多人在线匿名聊天的小程序

    最近看到好多设计类网站,都提供了多人在线匿名聊天的小功能,感觉很有意思,于是基于python的django框架自己写了一个,支持手动实时更名,最下方提供了完整的源码. 在线聊天地址(无需登录,开一个窗口,代表一个用户): http://zhaozhaoli.vicp.io/chatroom/happy/ 移动端聊天效果图: 网页版聊天效果图: 实现思路: 发送的消息通过ajax先写入数据库,通过ajax的循环请求,将写入数据库的消息显示到前端界面. 前端核心代码: <script> $(fun

  • django使用channels实现通信的示例

    1.安装依赖包 pip install channels channels-redis 2.settings.py 修改加上支持 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'MyW

  • Django Channel实时推送与聊天的示例代码

    先来看一下最终的效果吧 开始聊天,输入消息并点击发送消息就可以开始聊天了 点击 "获取后端数据"开启实时推送 先来简单了解一下 Django Channel Channels是一个采用Django并将其功能扩展到HTTP以外的项目,以处理WebSocket,聊天协议,IoT协议等.它基于称为ASGI的Python规范构建. 它以Django的核心为基础,并在其下面分层了一个完全异步的层,以同步模式运行Django本身,但异步处理了连接和套接字,并提供了以两种方式编写的选择,从而实现了这

  • vue+django实现一对一聊天功能的实例代码

    vue+django实现一对一聊天和消息推送的功能.主要是通过websocket,由于Django不支持websocket,所以我使用了django-channels.考虑到存储量的问题,我并没有把聊天信息存入数据库,服务端的作用相当于一个中转站.我只讲述实现功能的结构性代码,具体的实现还请大家看源代码. 前端聊天界面 前端消息提醒界面 后端代码 效果展示 实现过程 后端 首先,我们需要先定义websocket的两条连接路径.ws/chat/xxx/(xxx指代聊天组)这条路径是当聊天双方都进入

  • Django使用Channels实现WebSocket的方法

    WebSocket - 开启通往新世界的大门 WebSocket是什么? WebSocket是一种在单个TCP连接上进行全双工通讯的协议.WebSocket允许服务端主动向客户端推送数据.在WebSocket协议中,客户端浏览器和服务器只需要完成一次握手就可以创建持久性的连接,并在浏览器和服务器之间进行双向的数据传输. WebSocket有什么用? WebSocket区别于HTTP协议的一个最为显著的特点是,WebSocket协议可以由服务端主动发起消息,对于浏览器需要及时接收数据变化的场景非常

  • 详解Django-channels 实现WebSocket实例

    引入 先安装三个模块 pip install channels pip install channels_redis pip install pywin32 创建一个Django项目和一个app 项目名随意,app名随意.这里项目名为 django_websocket_demo ,app名 chat 把app文件夹下除了 views.py 和 __init__.py 的文件都删了,最终项目目录结构如下: django_websocket_demo/ manage.py django_websoc

  • 基于django channel实现websocket的聊天室的方法示例

    websocket 网易聊天室? ​ web微信? ​ 直播? 假如你工作以后,你的老板让你来开发一个内部的微信程序,你需要怎么办?我们先来分析一下里面的技术难点 消息的实时性? 实现群聊 现在有这样一个需求,老板给到你了,关乎你是否能转正?你要怎么做? 我们先说消息的实时性,按照我们目前的想法是我需要用http协议来做,那么http协议怎么来做那? 是不是要一直去访问我们的服务器,问服务器有没有人给我发消息,有没有人给我发消息?那么大家认为我多长时间去访问一次服务比较合适那? 1分钟1次?1分

  • 浅谈django channels 路由误导

    与django路由有区别 他们都有根路由,但是不一样. django的根路由: urlpatterns = [ path('login/',include('login.urls')), path('',views.home), path('helloapp/', include('helloapp.urls')), path('admin/', admin.site.urls), ] channels的根路由: 只能形如这种样子,URLRouter里面是一个列表,列表当中是具体路由条目. ap

  • django使用channels2.x实现实时通讯

    一.背景 在最近的项目中的一个需求是消息实时推送消息以及通知功能,项目使用django写的所以决定采用django-channels来实现websocket进行实时通讯.目前官方已经更新到2.1版本,相对于老的channels 1.x版本有了很大变化,无论是使用方式还是功能,其中最大的变化莫过于2.x版本中带来的asyncio特性,可使用异步处理模式.本文内容将介绍channels2版本使用,由于项目django是1.11,其中也遇到了一些坑,比如在channels在处理一次请求后hang住然后

随机推荐