Table of contents
If you don’t want to see my blind BB, you can jump directly here
3. Prepare the Django environment
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
4. Integration of channels in Django
4.3 Create a view function in chat/views.py
4.4 Create routes in chat/urls.py
4.5 WebSocket connection configuration
4.7 Enabling the channel layer
4.7.2 Configure the environment of the channel layer
4.7.3 Test whether the environment is configured successfully
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 poll
and 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 poll
In fact, the principle is similar to that of ajax
polling , 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.
ajax
Polling requires very fast processing and resources (speed) from the server. long poll
High concurrency is required, that is to say, the ability to receive customers at the same time (site size).
1.3 Websocket
WebSocket
Is a TCP
protocol for full-duplex communication over a single connection. WebSocket
Allows the server to actively push data to the client. In WebSocket
the 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
Django
Not natively supported WebSocket
, but can be Channels
implemented through the integration frameworkWebSocket
Channels
It is an enhanced framework for the Django project, which can Django
not only support the protocol , HTTP
but also support multiple protocols, and also integrate the system to facilitate user management and authentication.WebSocket
MQTT
Channels
Django
auth
session
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 Python
applications, capable of handling a variety of common protocol types, including HTTP
, HTTP2
and WebSocket
.
WSGI
It is based on HTTP
the protocol mode and does not support it WebSocket
, but ASGI
it was born to solve Python
the commonly used ones WSGI
that do not support Web
some new protocol standards currently under development. At the same time, the support and extension ASGI
of WSGI
the original model is the extension of the best.WebSocket
ASGI
WSGI
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 chat
application is installed. Edit mysite/settings.py
the 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.
在
templates
中
Create 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.py
the 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.py
Create 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.py
the file and add 'channels'
to INSTALLED_APPS
the 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.py
creating 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.py
creating 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 chat
create 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.py
Inside the file and CHANNEL_LAYERS
add 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/consumer
write the following code in . chat/consumers.py
to 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.
ChatConsumer
Further explanation of several parts of the code:
-
self.scope['url_route']['kwargs']['room_name']
'room_name'
Get parameters fromchat/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