Nanny level comes with source code Django integrated channels (1) to achieve simple chat function

Table of contents

foreword

If you don’t want to see my blind BB, you can jump directly here

1.WebSocket

1.1 ajax polling

1.2 long poll

1.3 Websocket

2.Channels

2.1 WSGI

2.2 ASGI

3. Prepare the Django environment

3.1 install channels

3.2 Create a Django project

 3.3 Create a chat application

3.4 Add index view

3.5 Create a view function in chat/views.py:

3.6 Create a file called urls.py in chat to store routes:

3.7 Configure the total route in mysite/urls.py

3.8 Start it and check

4. Integration of channels in Django

4.1 Configuration environment

4.3 Create a view function in chat/views.py

4.4 Create routes in chat/urls.py

4.5 WebSocket connection configuration

4.6 Configure socket routing

4.7 Enabling the channel layer

4.7.1 Install channels_redis

4.7.2 Configure the environment of the channel layer

4.7.3 Test whether the environment is configured successfully

4.8 Replacing old code

4.9 Testing

5. Source code reference


foreword

The vast majority of blogs on the Internet use the sending end or the receiving end as the server at the same time. Isn’t that ridiculous...or the messy ones can’t run at all, and I can’t stand it. After a period of study, I decided to write a nanny- level blog Integrated articles, with source code attached at the end of the article !

If you don’t want to see my blind BB, you can jump directly here

In order to achieve two-way communication , I choose websocket; but Django 3.0 does not support websocket when it goes up, so I am speechless , so here I implement websocket through channels.

1.WebSocket

        Before talking Websocket, let's understand the principles of long polland ajax轮询.

1.1 ajax polling

        The principle of ajax polling is very simple, let the browser send a request every few seconds to ask the server if there is new information. This is a waste of server resources. It’s okay if the project is small. If the project is bigger, the boss will let you go directly.

1.2 long poll

  long pollIn fact, the principle is similar to that of ajaxpolling , both of which use polling, but adopt a blocking model (keep calling, don’t hang up if you don’t receive it), that is to say, after the client initiates a connection, if there is no message, it will always Do not return a Response to the client. It does not return until there is a message. After returning, the client establishes a connection again, and the cycle repeats.

  ajaxPolling requires very fast processing and resources (speed) from the server. long pollHigh concurrency is required, that is to say, the ability to receive customers at the same time (site size).

1.3 Websocket

  WebSocketIs a TCPprotocol for full-duplex communication over a single connection. WebSocketAllows the server to actively push data to the client. In WebSocketthe protocol, the client browser and the server only need to complete a handshake to create a persistent connection and perform two-way data transmission between the browser and the server.

2.Channels

  DjangoNot natively supported WebSocket, but can be Channelsimplemented through the integration frameworkWebSocket

  ChannelsIt is an enhanced framework for the Django project, which can Djangonot only support the protocol , HTTPbut also support multiple protocols, and also integrate the system to facilitate user management and authentication.WebSocketMQTTChannelsDjangoauthsession

2.1 WSGI

WSGI(Python Web Server Gateway Interface)Web: A simple and general interface between web servers and applications or frameworks defined for the Python language .

2.2 ASGI

ASGI(Asynchronous Web Server Gateway Interface): Asynchronous gateway protocol interface, a standard interface between network protocol services and Pythonapplications, capable of handling a variety of common protocol types, including HTTP, HTTP2and WebSocket.

WSGIIt is based on HTTPthe protocol mode and does not support it WebSocket, but ASGIit was born to solve Pythonthe commonly used ones WSGIthat do not support Websome new protocol standards currently under development. At the same time, the support and extension ASGIof WSGIthe original model is the extension of the best.WebSocketASGIWSGI

3. Prepare the Django environment

This article was written for Channels 3.0 , which supports Python 3.6+ and Django 2.2+

python3 -m django --version

3.1 Installationchannels

pip install channels

3.2 Create a Django project

 3.3 Create a chat application

python manage.py startapp chat

 We need to tell our project that the chatapplication is installed. Edit  mysite/settings.pythe file and add 'chat'to INSTALLED_APPS setting. It looks like this:

3.4 Add index view

We will now create our first view, an index view that allows you to type the name of the chat room you want to join.

templatesCreate a folder named chat in the directory . Used to store html pages.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br>
    <input id="room-name-input" type="text" size="100"><br>
    <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>

3.5 When chat/views.py中creating a view function:

# chat/views.py
from django.shortcuts import render

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

3.6 After chat中creating a file called urls.py用来存放路由:

# chat/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

 

3.7 Configure the total route in mysite/urls.py

# mysite/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),
]

3.8 Start it and check

 

Django is ready for success!

4. Integration of channels in Django

So far, we've just created a regular Django application; we haven't used the Channels library at all. Now is the time to integrate.

I start by creating a root routing configuration for Channels. The Channels routing configuration is an ASGI application similar to Django's URLconf in that it tells Channels what code to run when the Channels server receives an HTTP request.

4.1 Configuration environment

under mysite/asgi.pythe file

Without this python file, you can manually create one yourself

# mysite/asgi.py
import os

from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application

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

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # Just HTTP for now. (We can add other protocols later.)
})

 Notice:

     Django 2.2 doesn't have built-in ASGI support, so we need to use a fallback alternative to Channel. mysite/asgi.pyCreate it like this:

# mysite/asgi.py
import os

import django
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django.setup()

application = ProtocolTypeRouter({
  "http": AsgiHandler(),
  # Just HTTP for now. (We can add other protocols later.)
})

Now add Channel Library to the list of installed applications. Edit mysite/settings.pythe file and add 'channels'to  INSTALLED_APPSthe settings, you also need to point Channels to the root routing configuration

 Start a new one, the following configuration is successful.

4.2 Add View

Add a new html page room.html in templates/ chat

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br>
    <input id="chat-message-input" type="text" size="100"><br>
    <input id="chat-message-submit" type="button" value="Send">
    {
   
   { room_name|json_script:"room-name" }}
    <script>
        const roomName = JSON.parse(document.getElementById('room-name').textContent);

        const chatSocket = new WebSocket(
            'ws://'
            + window.location.host
            + '/ws/chat/'
            + roomName
            + '/'
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (data.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) {
            const messageInputDom = document.querySelector('#chat-message-input');
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                'message': message
            }));
            messageInputDom.value = '';
        };
    </script>
</body>
</html>

4.3 When chat/views.pycreating a view function

# 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
    })

4.4 In chat/urls.pycreating a route

# 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'),
]

4.5 WebSocket connection configuration

Notice:

/ws/It is good practice to use a    common path prefix (for example to distinguish WebSocket connections from normal HTTP connections), as it will make it easier to deploy Channels to production in certain configurations.

   create a new filechat/consumers.py

 put the following code intochat/consumers.py

# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        self.send(text_data=json.dumps({
            'message': message
        }))

4.6 Configure socket routing

Now also need to chatcreate routing config for application with routing to consumer

     create a new filechat/routing.py

 put the following code intochat/routing.py

# 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()),
]

   The class method is called as_asgi()to get an ASGI application that will instantiate our consumer instance for each user connection. This is similar to Django's as_view(), which does the same for each requested Django view instance.

The next step is to point the root routing configuration to  the chat.routing module

# mysite/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", "mysite.settings")

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

At this time, after it has been configured and running, it is found that it cannot chat at all! !

Isn't this the end? . . . . . .

Solution:

4.7 Enabling the channel layer

We'll use a channel layer that uses Redis as its backing store. To start the Redis server

If there is an error in starting redis, please see my other article: Solution to Redis server startup under windows in a flash

4.7.1 Install channels_redis

so that Channels know how to interact with Redis

pip install channels_redis

4.7.2 Configure the environment of the channel layer

mysite/settings.pyInside the file and CHANNEL_LAYERSadd a setting at the bottom

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

4.7.3 Test whether the environment is configured successfully

import channels.layers
channel_layer = channels.layers.get_channel_layer()
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
async_to_sync(channel_layer.receive)('test_channel')

#结果:
{'type': 'hello'}

4.8 Replacing old code

 Now that we have a channel layer, /chat/consumerwrite the following code in . chat/consumers.pyto replace the old code

# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

    When a user posts a message, the JavaScript function will transmit the message to the ChatConsumer via WebSocket. ChatConsumer will receive the message and forward it to the group corresponding to the room name. Each ChatConsumer in the same group (and therefore in the same room) receives messages from the group, forwards them back to JavaScript via WebSocket, and appends them to the chat log, enabling chat functionality.

ChatConsumerFurther explanation of several parts of the code:

  • self.scope['url_route']['kwargs']['room_name']

    • 'room_name'Get parameters from chat/routing.py the URL route that opens the WebSocket connection to the consumer.
    • Each consumer has a scope that contains information about its connection, including in particular any positional or keyword arguments from the URL route and the currently authenticated user (if any).
  • self.room_group_name = 'chat_%s' % self.room_name

    • Constructs a Channels group name directly from the user-specified room name, without any quoting or escaping.
    • Group names can only contain letters, numbers, hyphens, and periods. Therefore, this example code will fail on room names that contain other characters.
  • async_to_sync(self.channel_layer.group_add)(...)

    • Join a group.
    • The async_to_sync(...) wrapper is needed because the ChatConsumer is a synchronous WebsocketConsumer, but it is calling an asynchronous channel layer method. (All channel layer methods are asynchronous.)
    • Group names are limited to ASCII alphanumerics, hyphens, and periods. Since this code constructs a group name directly from the room name, it will fail if the room name contains any characters that are not valid in a group name.
  • self.accept()

    • Accept WebSocket connections.
    • If you do not call accept() in the connect() method, the connection will be rejected and closed. For example, you might want to deny a connection because the requesting user is not authorized to perform the requested operation.
    • If you choose to accept connections, it is recommended to call accept() as the last operation in connect() .
  • async_to_sync(self.channel_layer.group_discard)(...)

    • Leave a group.
  • async_to_sync(self.channel_layer.group_send)

    • Send an event to the group.
    • An event has a special 'type'key corresponding to the name of the method that should be called on the consumer that received the event.

4.9 Testing

 So far, the integration of simple channels in Django has been completed. 

The next article will talk about Django integrated channels to realize real-time video transmission of socket camera

5. Source code reference

Code cloud: Django integrated channels: Django integrated channels series

Guess you like

Origin blog.csdn.net/weixin_46504244/article/details/121327673