برنامه نویسی

WebSocket در جنگو – انجمن DEV

WebSocket چیست؟

خوب، آنها پروتکل ارتباطی کامپیوتری هستند که برای برقراری ارتباط دو طرفه استفاده می شود.
می تواند توسط سرور برای ارسال داده به مشتری استفاده شود.

به طور خلاصه، از آنها برای ارتباط بلادرنگ استفاده می شود.
به عنوان مثال: برنامه های چت، همگام سازی داده های بلادرنگ در بازی ها، SSE و غیره.

به نظر می رسد WebSocket جالب به نظر می رسد، آنها می توانند برای برنامه های ساده مانند یک برنامه آب و هوا که داده ها را بین چند بازه نظرسنجی می کند بیش از حد پر شده باشند.

چگونه می توانید آنها را در جنگو پیاده سازی کنید؟

خوب، به راحتی می توانیم از یک بسته پایتون به نام استفاده کنیم channels.
آنها توسط همان توسعه دهندگانی ساخته شده اند که جنگو را ساخته اند.

فقط یک ترمینال را باز کنید و برای نصب کانال ها عبارت زیر را تایپ کنید

python -m pip install -U channels["daphne"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این کانال ها و یک وب سرور ویژه ASGI به نام نصب می کند daphne که برای رسیدگی به آن استفاده می شود ws:// پروتکل

اکنون در این پست، یک نشانگر حضور کاربر آنلاین ایجاد می کنیم. این برنامه تعداد کاربران متصل به سرور را به مشتری می گوید.

همچنین ما از آن استفاده نخواهیم کرد channel_layer برای پخش پیام، زیرا اوضاع را بیش از حد پیچیده می کند.

یک برنامه جنگو ایجاد کنید

django-admin startproject django_websocket
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برو به django_websocket پوشه و این دستور را تایپ کنید تا یک برنامه جنگو جدید ایجاد کنید.

cd django_websocket
python manage.py startapp presence
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

که در settings.py اضافه کردن daphne سرور و ما presence برنامه

اضافه کردن daphne به بالا.

# django_websocket/settings.py

INSTALLED_APPS = [
    'daphne' # ⬅ ASGI Webserver
    'presence', # ⬅ Our Custom App
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خطوط زیر را در قسمت اضافه کنید settings.py به جنگو اجازه می دهد تا ASGI را نیز مدیریت کند و همچنین تغییر مسیر ورود و خروج را مدیریت کند.

# django_websocket/settings.py
# Daphne Conf
ASGI_APPLICATION = "django_websocket.asgi.application"

LOGIN_REDIRECT_URL = "index"
LOGOUT_REDIRECT_URL = "index"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا بیایید کمی کد اضافه کنیم asgi.py و کد را با این جایگزین کنید:

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter # <- Add this
from channels.auth import AuthMiddlewareStack # <- Add this
from presence.consumers import PresenceConsumer # <- Add this

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_websockets.settings')

application = ProtocolTypeRouter(
    {
        "http": get_asgi_application(),
        "websocket": AuthMiddlewareStack(
            PresenceConsumer.as_asgi()
        ),
    }
)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید آن را تجزیه کنیم:

  • از خط 1 به 6 ما همه ماژول های مورد نیاز را وارد می کنیم
    • os: برای تنظیم برخی از متغیرهای محیطی مورد نیاز
    • get_asgi_application: فقط سمت http برنامه ما را کنترل می کند.
    • ProtocolTypeRouter: به ما اجازه می دهد تا انواع مختلفی از رابط های پروتکل را مدیریت کنیم، مانند for http ما فقط کارهای http را انجام می دهیم، اما برای اتصالات پروتکل WebSocket به برنامه ما اجازه می دهیم تا با بخش وب سوکت ها کار کند.
    • AuthMiddlewareStack: از احراز هویت استاندارد جنگو پشتیبانی می کند، جایی که جزئیات کاربر در جلسه ذخیره می شود. این اجازه می دهد تا دسترسی فقط خواندنی به یک شی کاربر در scope. به طور خلاصه به ما امکان می دهد جریان را دریافت کنیم request.user
    • PresenceConsumer: مصرف کننده ASGI سفارشی ما است که سوکت های وب را مدیریت می کند. در ادامه این را خواهیم ساخت
    • خط 8 جایی است که ما در حال اضافه کردن فایل هستیم و پروژه را اضافه می کنیم settings.py در env به نام DJANGO_SETTINGS_MODULE. ما نباید نگران آن باشیم.
    • از خط 10 به 17 جایی است که ما برنامه را اعلام می کنیم، همان است "django_websocket.asgi.application.
  • برنامه برابر است با ProtocolTypeRouter که دیکشنری از پروتکل هایی مانند http و websocket
  • WebSocket تابعی به نام می گیرد AuthMiddlewareStack که مصرف کنندگان ما را می گیرد

اکنون به بخش جالبی می‌رسیم که خود WebSocket را به مصرف‌کنندگان تبدیل می‌کند.

ابتدا یک نام فایل ایجاد کنید consumers.py که در presence پوشه، و این کد را در آن بنویسید.

import json

from channels.generic.websocket import WebsocketConsumer


class PresenceConsumer(WebsocketConsumer):

    connections = []

    def connect(self):
        self.accept()
        self.user = self.scope["user"]
        self.connections.append(self)
        self.update_indicator(msg="Connected")

    def disconnect(self, code):
        self.update_indicator(msg="Disconnected")
        self.connections.remove(self)
        return super().disconnect(code)

    def update_indicator(self, msg):
        for connection in self.connections:
            connection.send(
                text_data=json.dumps(
                    {
                        "msg": f"{self.user} {msg}",
                        "online": f"{len(self.connections)}",
                        "users": [f"{user.scope['user']}" for user in self.connections],                        
                    }
                )
            )

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون این کد ممکن است ترسناک به نظر برسد، اما در واقع زمانی که مفهوم اصلی Consumers را درک کنید، بسیار ساده است.

بیایید آن را تجزیه کنیم.

  • از خط 1 تا 3، ما چند ماژول مهم مانند json و WebsocketConsumer.
  • از خط 6 ما در حال ایجاد یک کلاس به نام هستیم PresenceConsumer که از WebsocketConsumer

    • این WebsocketConsumer برخی از روش‌های WebSocket را به ما می‌دهد
      • connect متد زمانی فراخوانی می شود که کلاینت به WebSocket متصل شود. اگر این روش را نادیده بگیریم، باید اضافه کنیم self.accept().
      • disconnect متد زمانی فراخوانی می شود که کلاینت به WebSocket قطع شود.
      • receive متد زمانی فراخوانی می شود که مشتری درخواستی را به WebSocket ارسال کند
  • روش نامگذاری شده update_indicator یک روش سفارشی است که ما ایجاد کرده ایم.

  • این connections یک لیست خالی است که کاربران متصل را ذخیره می کند.

در connect روش، ما روش‌ها را نادیده می‌گیریم تا وقتی یک کاربر جدید به WebSocket متصل شد، کاربر را از جلسه با این کد دریافت می‌کنیم. self.user = self.scope["user"]، همانطور که خود را بسته ایم PrescenceConsumer که در AuthMiddlewareStack، می توانیم کاربر فعلی را از جلسه دریافت کرده و در قسمت ذخیره کنیم self.user. پس از دریافت کاربر، می‌خواهیم اتصال کاربر فعلی را به آن اضافه کنیم connection و سپس فراخوانی self.update_indicator و عبور از msg مانند connected.

حال اگر به داخل نگاه کنیم update_indicator ما می توانیم ببینیم که ما در حال حلقه زدن به آن هستیم connection لیست و ارسال تمام مشتری یک داده json.

مثال:
اگر کاربری به نام Sid با 3 کاربر متصل به WebSocket متصل شود، همه کلاینت های متصل به WebSocket از جمله Sid، json زیر را دریافت خواهند کرد.

{
    "msg": "Sid Connected",
    "online": 4,
    "users" [
                "user1",
                "user2",
                "user3",
                "Sid"
            ]
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون برخی از نماهای خود را برای نمایش صفحه فهرست و صفحه ورود بررسی می کنیم. فقط این 4 خط کد را اضافه کنید و کارتان تمام است

from django.shortcuts import render

def index(request):
    return render(request, "index.html")
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بله همین است.

اکنون به مسیریابی WebSocket می پردازیم.

این دقیقاً مانند نقشه‌برداری آدرس اینترنتی از نماهای شما در یک برنامه معمولی جنگو است.

در حال حاضر بیشتر در برنامه های کاربردی در مقیاس بسیار بزرگ، ما فقط باید مسیریابی view ها و مسیریابی ws را از هم جدا کنیم، اما در اینجا برای صرفه جویی در زمان، فقط می خواهیم آن را در urls.py

  • ابتدا a ایجاد کنید urls.py فایل در presence برنامه و کد زیر را اضافه کنید.
from django.urls import path

from . import consumers
from . import views
from django.contrib.auth.views import LoginView, LogoutView


urlpatterns = [
    path('', views.index, name="index"),
    path('login/', LoginView.as_view(template_name='login.html'), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path("ws/presence", consumers.PresenceConsumer.as_asgi()),
]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا ما در حال ایجاد یک نقشه URL برای views و WebSocket خود هستیم.
همانطور که می بینید، ما همچنین از نماهای احراز هویت جنگو برای دریافت قابلیت ورود و خروج در برنامه خود استفاده می کنیم.

برای اضافه کردن WS Consumer، فقط باید آن را در نقشه برداری کنیم urlpatterns درست مانند هر نمای کلاس دیگری، فقط به جای .as_view()، اینجا می نویسیم .as_asgi().

اوه! این قسمت باطن بود، حالا باید قسمت جلویی خود را راه اندازی کنیم.

حالا بیایید به قسمت جلویی برسیم

  • ایجاد یک template دایرکتوری در presence برنامه و ایجاد دو فایل به نام index.html و login.html

در index.html کد زیر را بنویسید

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>Online Presence Indicator</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>

<body>
  <div class="container">
    <section class="section">
      <div class="columns">
        <div class="column">

          {% if user.is_authenticated %}
          Hello <strong>{{ user }}</strong>
          <br>
          <a href="{% url 'logout' %}">Logout</a>
          {% else %}
          <a href="{% url 'login' %}">Login</a>
          {% endif %}
          <h1 class="title">Online Presence Indicator</h1>
          <div id="presence"><span class="tag is-success" id="pre_cnt">0</span> users online</div>
          <ul id="messages"></ul>
        </div>
        <div class="column">
          <div class="box">
            <h1 class="title">Online Users</h1>
            <div id="online-users"></div>
          </div>
        </div>
      </div>
    </section>
  </div>

  <script>
    const ws = new WebSocket('ws://localhost:8000/ws/presence/');
    const presenceEl = document.getElementById('pre_cnt');
    const messagesEl = document.getElementById('messages');
    const onlineUsers = document.querySelector("#online-users");

    ws.onmessage = (event) => {
      onlineUsers.innerHTML = "";
      let data = JSON.parse(event.data)
      presenceEl.innerHTML = data.online;
      const li1 = document.createElement('li');
      li1.innerHTML = data.msg;
      messagesEl.appendChild(li1);
      data.users.forEach(user => {
        const li2 = document.createElement("li");
        li2.classList.add("on-us")
        li2.innerHTML = user;
        onlineUsers.appendChild(li2);
      });

    };
  </script>
</body>

</html>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا ما از Bulma برای استایل دادن به صفحه خود استفاده می کنیم، در صورت تمایل می توانید از شیوه نامه خود استفاده کنید.

نکته اصلی که در اینجا باید به آن نگاه کنیم جاوا اسکریپت است.

  • در ابتدا ما در حال ایجاد یک WebSocket شی و ارسال url WebSocket.
  • سپس عناصر html را با استفاده از آن ذخیره می کنیم getElementById.
  • سپس ما هستیم WebSocket.onmessage دسته رویداد به پیام های بلادرنگ از سرور. این event حاوی الف data ویژگی، که برای آن استفاده خواهیم کرد JSON.parse برای تبدیل JSON آینده به شی
  • هنگامی که WebSocket پیامی را از سرویس دریافت می کند، نشان خواهیم داد که کاربر به آن متصل است #messages div و کاربر را به آن اضافه کنید #online-users بخش

حالا بیایید به سرعت صفحه ورود خود را ایجاد کنیم.
باز کن login.html و این html را بنویسید.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>Online Presence Indicator</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>

<body>
  <div class="container">
    <section class="section">
        <h1 class="title">Login</h1>
      <form action="." method="post">
        {{form.as_p}}
        {% csrf_token %}
        <br>
        <button class="button">Login</button>
      </form>
    </section>
  </div>
</body>

</html>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

فقط همین و اکنون درخواست ما کامل شده است.

حالا بیایید با اجرای سرور آن را آزمایش کنیم

python manage.py runserver
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما باید جریان زیر را ببینید.

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 23, 2023 - 22:54:21
Django version 4.2.1, using settings 'myproject.settings'
Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

می بینیم که جنگو از سرور ASGI/Daphne برای اجرای این وب سایت استفاده می کند.

اکنون http://localhost:8000 را باز کنید و باید صفحه زیر را ببینید.

صفحه فهرست
صفحه فهرست با کاربر احراز هویت نشده

خواهید دید که جعبه پیام نوشته شده است AnonymousUser Connected

بیایید یک حساب کاربری ایجاد کنیم و آن را آزمایش کنیم.

python manage.py createsuperuser
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا که superuser را ایجاد کردیم، بیایید با این حساب وارد شوید و ببینید.

صفحه ورود
صفحه ورود

صفحه فهرست با کاربر تایید شده
صفحه فهرست با کاربر تایید شده

حالا یک صفحه خصوصی باز کنید و هر دو صفحه را در نمای تقسیم شده باز کنید

تقسیم صفحه

ویدئوی نمایشی
https://www.youtube.com/watch?v=evlpSU-qQoI

این لینک GitHub برای کد منبع این برنامه است: https://github.com/foxy4096/django_websocket_demo

پس این برای آن بچه ها.

به سلامتی ✌

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا