Table of contents
1. Create project mysite and apply chat
2. Create an application chat and add the application to INSTALLED_APPS of settings.py
5. Configure the root route to specify the route of the chat application
Second, implement the chat server
1. Create a new file chat/templates/chat/room.html and add the following content
2. Create view room and configure routing
3. Configure consumer consumers.py
6. Enable channel layer CHANNEL_LAYERS
7. Configure consumers.py again
3. Improvement: rewrite the consumer as asynchronous
Version
python==3.7
django==3.2.18
channels==3.0.3
channels-redis==4.0.0
1. Create project mysite and apply chat
1. Create project mysite
Use pycharm to create project mysite
Create the project mysite with the command
django-admin startproject mysite
2. Create an application chat and add the application to INSTALLED_APPS of settings.py
python manage.py startup app chat
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'chat',
]
The directory structure of the project after completion:
3. Add template files
Create a new templates folder under the chat application, and right-click to select Mark Direcory as >> Template Folder, the templates folder will turn purple, and create an index.html file
index.html add the following content
<!-- chat/templates/chat/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>
4. Add views and routes
# chat/views.py
from django.shortcuts import render
def index(request):
return render(request, "index.html")
# chat/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
]
5. Configure the root route to specify the route of the chat application
# mysite/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("chat/", include("chat.urls")),
path("admin/", admin.site.urls),
]
Start the django project at this time, and the browser visits http://127.0.0.1:8000/chat/
Here, after entering the string in the input box and clicking Enter, a 404 error will be reported, so let's continue to configure
6. Integrated channels
a. Install channels
pip3 install -i https://pypi.douban.com/simple channels==3.0.3
Specify to install channels==3.0.3, do not install the latest 4.0 version, otherwise websocket will report a 404 error when connecting
b. Adjust the code in 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(),
}
)
c. Add channels application and configure ASGI_APPLICATION in settings.py
# mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'chat',
'channels'
]
ASGI_APPLICATION = "mysite.asgi.application"
Second, implement the chat server
1. Create a new filechat/templates/chat/room.html,并添加以下内容
<!-- chat/templates/chat/room.html -->
<!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>
2. Create view room and configure routing
# 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, "room.html", {"room_name": 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"), # 新添加
]
At this time, start the django project, open the console in the browser and enter the address http://127.0.0.1:8000/chat/
Type in myroom and Enter
Enter a message and click Send and nothing happens, so go ahead and configure consumers
3. Configure consumer consumers.py
Add a new one under the chat applicationconsumers.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}))
Directory Structure
4. Configure routings.py
Create a new routing.py file under the chat application and add the following content
# 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()),
]
5. Configure asgi.py again
# mysite/asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from chat.routing import websocket_urlpatterns
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
import chat.routing
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
),
}
)
Run the migration command
python manage.py migrate
At this time, start the django project, open the console in the browser to access http://127.0.0.1:8000/, enter myroom and enter, and no longer report the error of ws connection 500
At this time, enter a message and click Send, the message will appear in the chat box, but when you open a new tab and enter the same URL (http://127.0.0.1:8000/chat/myroom/), the message will not appear in the In the URL message chat box of the new tab page
To be able to receive messages sent by another tab page, you need to continue to configure the startup channel layer CHANNEL_LAYERS
6. Enable channel layer CHANNEL_LAYERS
- Install channels-redis first
pip3 install -i https://pypi.douban.com/simple channels-redis==4.0.0
- Add CHANNEL_LAYERS configuration in settings.py (please confirm that redis has been installed before this)
# settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
- Make sure the channel layer can communicate with Redis. Open the Django shell and run the following command:
$ python3 manage.py shell >>> 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'}
7. Configure consumers.py again
Replace the code in chat/consumers.py with the following 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 = f"chat_{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}))
At this time, start the django project, open two tab pages in the browser, and enter http://127.0.0.1:8000/chat/myroom/ , send the message again, and the other tab page will receive it~
So far, a basic full-featured chat server is complete
3. Improvement: rewrite the consumer as asynchronous
The code in consumers.py we wrote before is all synchronous. Synchronous uses are convenient because they can call regular synchronous I/O functions such as those that access Django models without writing special code. However, asynchronous consumers can provide a higher level of performance, because they do not need to create additional threads when processing requests.
So we rewrite the code in chat/consumers.py again, replacing the synchronous code written before with the following code
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = f"chat_{self.room_name}"
# Join room group
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat.message", "message": message}
)
# Receive message from room group
async def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))
This new code for ChatConsumer is very similar to the original code, with the following differences:
ChatConsumer
Now inherits fromAsyncWebsocketConsumer
, instead ofWebsocketConsumer
.- All methods are
async def
and not onlydef
. await
Used to call asynchronous functions that perform I/O.当
async_to_sync is no longer required when calling methods at the channel layer.
At this time, start the django project again, open two tabs in the browser, and enter http://127.0.0.1:8000/chat/myroom/ , and send the message again. At this time, the chat server is completely asynchronous
Reference: Tutorial — Channels 4.0.0 documentation