使用Django频道和Websocket构建实时聊天应用程序
#python #django #websockets

本文最初是由Muhammed AliHoneybadger Developer Blog上撰写的。

Django以用于开发用于HTTP连接的服务器和应用程序的请求而闻名。不幸的是,当构建需要连接的应用程序以保持双向连接(例如会议和聊天应用程序)时,使用HTTP连接效率低下。这是WebSockets发挥作用的地方。

WebSocket提供了一种在客户端和服务器之间打开双向连接的方法,以便所有连接到开放网络的用户都可以实时获取相关数据。这是一个状态协议,这意味着仅需要一次连接身份验证;客户凭据存储,并且在连接丢失之前不需要进一步的身份验证。

在本文中,我将简要介绍您的Websocket及其有用性。然后,我将向您展示如何使用Django频道在Django中使用它,并与JavaScript创建WebSocket连接以与Django服务器连接。

我们将构建一个简单的聊天框,以使事情更现实。您可以在GitHub上找到代码。

先决条件

  • 对Django的基本理解。
  • 对JavaScript的基本理解。

Websocket的特征

  • WebSockets是双向协议。因此,客户端和服务器可以同时交换数据,而无需延迟或干预。由于相同的原因,WebSocket被视为全双工通信。
  • WebSockets是一个状态协议。因此,在初始连接身份验证后,保存客户端凭据,并且在连接丢失之前不需要进一步的身份验证。
  • Websockets不需要任何特殊的浏览器才能运行;它适用于所有浏览器。

何时使用WebSockets

当您想构建任何类型的实时应用程序时,都会使用WebSockets,从互联网上播放的多人游戏等复杂应用程序到诸如聊天应用程序之类的复杂应用程序。

不使用WebSocket的构建聊天应用程序的另一种方法是使用JavaScript在几秒钟后查询数据库,以从聊天框中获取当前数据。可以想象,这是不可扩展的,因为如果有成千上万的用户,它可能生成的请求数可能会导致服务器崩溃。此外,当您要构建视频通话应用程序之类的内容时,此方法不会。

如何在Django中使用Websocket

使用Django中的Websocket使用异步Python和Django频道,使过程直接。使用Django频道,您可以创建一个ASGI服务器,然后创建一个组,用户可以实时向组中的所有其他用户发送文本消息。这样,您不会与特定用户进行通信,但是可以添加组。

如果您在Twitter上与您的朋友聊天,那么您和您的朋友在一个组中,由Chatbox代表。

配置Django以使用ASGI

如果您已经有一个django项目,请创建一个文件夹,您想在其中存储项目代码,cd进入其中,然后运行startproject创建一个新的django项目:

django-admin startproject project .

现在,通过运行$ python3 manage.py startapp app创建一个新的django应用。

您需要通知您的Django项目,添加了一个新应用程序。为此,请更新project/settings.py文件,然后将âkude4添加到 INSTALLED_APPS 列表中。看起来像这样:

# project/settings.py
INSTALLED_APPS = [
   ...
   'chat',
]

现在通过在命令行上运行以下命令来安装Django channels

pip install channels

更新project/settings.py文件,然后将'channels'添加到INSTALLED_APPS列表:

# project/settings.py
INSTALLED_APPS = [
   ...
   'channels',
]

settings.py文件中时,您需要设置配置以启用Django Channel和Django使用Message Broker相互通信。我们可以为此使用像Redis这样的工具,但是对于本教程,我们将使用本地后端。将以下代码粘贴到您的settings.py文件中:

ASGI_APPLICATION = "project.routing.application" #routing.py will be created later
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': "channels.layers.InMemoryChannelLayer"
        }
    }

在上面的代码中,需要ASGI_APPLICATION运行ASGI服务器并告诉Django发生事件时该怎么办。此配置将放置在名为routing.py的文件中。

建立简约的聊天框

接下来,我们将创建一个聊天框,可以通过URL访问身份验证的用户并相互聊天。要实现此操作,请打开您的app/views.py文件并粘贴下面的代码以将chatbox名称从URL传递到html文件(chatbox.html):

from django.shortcuts import render

def chat_box(request, chat_box_name):
    # we will get the chatbox name from the url
    return render(request, "chatbox.html", {"chat_box_name": chat_box_name})

现在,用以下代码替换您在project/urls.py中拥有的代码。这将处理浏览器(http://127.0.0.1:8002/chat/**chatboxname**/)中所述的聊天框名称。

from django.contrib import admin
from django.urls import path
from app.views import chat_box

urlpatterns = [
    path("admin/", admin.site.urls),
    path("chat/<str:chat_box_name>/", chat_box, name="chat"),
]

接下来,让我们开始在consumers上工作。渠道中的消费者有助于将您的代码作为一系列功能构建,每当发生事件发生时。消费者通常用异步python编写。首先,在名为consumers.pyapp/文件夹中创建一个新文件,并粘贴下面所示的代码。以下代码正在处理服务器连接,断开连接,接收请求或发送文本时发生的事情。

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatRoomConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.chat_box_name = self.scope["url_route"]["kwargs"]["chat_box_name"]
        self.group_name = "chat_%s" % self.chat_box_name

        await self.channel_layer.group_add(self.group_name, self.channel_name)

        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.group_name, self.channel_name)
    # This function receive messages from WebSocket.
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        username = text_data_json["username"]

        await self.channel_layer.group_send(
            self.group_name,
            {
                "type": "chatbox_message",
                "message": message,
                "username": username,
            },
        )
    # Receive message from room group.
    async def chatbox_message(self, event):
        message = event["message"]
        username = event["username"]
        #send message and username of sender to websocket
        await self.send(
            text_data=json.dumps(
                {
                    "message": message,
                    "username": username,
                }
            )
        )

    pass

现在,让我们添加routing.py的代码,这是前面提到的。在您的/project文件夹中创建一个名为routing.py的文件并粘贴以下代码:

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from app import consumers

# URLs that handle the WebSocket connection are placed here.
websocket_urlpatterns=[
                    re_path(
                        r"ws/chat/(?P<chat_box_name>\w+)/$", consumers.ChatRoomConsumer.as_asgi()
                    ),
                ]

application = ProtocolTypeRouter( 
    {
        "websocket": AuthMiddlewareStack(
            URLRouter(
               websocket_urlpatterns
            )
        ),
    }
)

接下来,让我们为应用程序构建前端。在app/templates中创建一个名称chatbox.html的文件,然后粘贴下面显示的代码。此代码大部分是Bootstrap的stater template代码,只是为了给应用程序提供一些样式。

<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
        integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

</head>

<body>

    <div class="container">
        <div class="row d-flex justify-content-center">
            <div class="col-3">
                <form>
                    <div class="form-group">
                        <label for="exampleFormControlTextarea1" class="h4 pt-5">Chatbox</label>
                        <textarea class="form-control" id="chat-text" readonly rows="10"></textarea><br>
                    </div>
                    <div class="form-group">
                        <input class="form-control" placeholder="Enter text here" id="input" type="text"></br>
                    </div>
                    <input class="btn btn-primary btn-lg btn-block" id="submit" type="button" value="Send">
                </form>
            </div>
        </div>
    </div>
    {% comment %} Get data for username and chatbox name{% endcomment %}
    {{ request.user.username|json_script:"user_username" }}
    {{ chat_box_name|json_script:"room-name" }}


    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous">
    </script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous">
    </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
        integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous">
    </script>
</body>

</html>

接下来,我们将开发JavaScript代码,该代码将获取数据并从前端侧处理Websocket连接。将以下代码粘贴到您刚创建的HTML文件中的第一个<script>标签上方。

<script>
   const user_username = JSON.parse(document.getElementById('user_username').textContent);
   document.querySelector('#submit').onclick = function (e) {
      const messageInputDom = document.querySelector('#input');
      const message = messageInputDom.value;
      chatSocket.send(JSON.stringify({
          'message': message,
          'username': user_username,
      }));
      messageInputDom.value = '';
   };

   const boxName = JSON.parse(document.getElementById('box-name').textContent);
   # Create a WebSocket in JavaScript.
   const chatSocket = new WebSocket(
      'ws://' +
      window.location.host +
      '/ws/chat/' +
      boxName +
      '/'
   );

   chatSocket.onmessage = function (e) {
      const data = JSON.parse(e.data);
      document.querySelector('#chat-text').value += (data.message + ' sent by ' + data.username   + '\n') // add message to text box
   }
</script>

现在,运行以下命令以迁移身份验证模型,以便您可以创建新用户来测试应用程序:

python manage.py migrate

您可以通过运行以下内容来创建新用户:

python manage.py createsuperuser

最后,您可以使用Django管理员运行并登录两个用户来测试该应用程序。您将可以通过登录不同浏览器的每个用户来做到这一点。然后,在每个浏览器上打开URL 127.0.0.1:8000/chat/newbox/,当您发送文本时,每个用户会实时接收文本。

output of the final app

结论

在本文中,您了解了Websocket及其有用性,以及如何使用Django频道在Django中使用它。最后,您学会了如何与JavaScript创建WebSocket连接以与Django服务器连接。

尽管我们成功地构建了一个实时聊天应用程序,但您还可以添加更多。例如,您可以添加一个database connection来存储消息。您还可以考虑使用Redis as the message broker而不是本地后端。