赞
踩
目录
2. 创建应用chat并添加应用到settings.py的INSTALLED_APPS中
1. 创建一个新文件chat/templates/chat/room.html,并添加以下内容
版本
python==3.7
django==3.2.18
channels==3.0.3
channels-redis==4.0.0
使用pycharm创建项目mysite
使用命令创建项目mysite
django-admin startproject mysite
python manage.py startapp chat
- # settings.py
-
- INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'chat',
- ]
完成之后项目的目录结构:
在chat应用下新建templates文件夹,并右键选择Mark Direcory as >> Template Folder,templates的文件夹将变为紫色,并创建index.html文件
index.html添加以下内容
- <!-- 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>
- # 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"),
- ]
- # 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),
- ]
此时启动django项目,浏览器访问http://127.0.0.1:8000/chat/
这里在输入框中输入字符串后点击Enter会报404的错误,所以我们接下来继续配置
a.安装channels
pip3 install -i https://pypi.douban.com/simple channels==3.0.3
指定安装channels==3.0.3,不要安装最新的4.0版本,否则websocket在连接时会报404错误
b. 调整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.在settings.py中添加channels应用及配置ASGI_APPLICATION
- # 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"
chat/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>
- # 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"), # 新添加
- ]
此时启动django项目,浏览器打开控制台输入地址http://127.0.0.1:8000/chat/
输入myroom,并Enter
输入消息并点击Send没有任何事发生,所以接下来继续配置消费者consumers
在chat应用下添新建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}))
目录结构
在chat应用下新建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()),
- ]
- # 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))
- ),
- }
- )
运行迁移命令
python manage.py migrate
此时启动django项目,浏览器打开控制台访问 http://127.0.0.1:8000/,输入myroom并Enter后,不再报ws连接500的错误了
此时输入消息并点击Send,消息将出现在聊天框中,但是打开新标签页输入同一网址(http://127.0.0.1:8000/chat/myroom/)时,再发消息并不会出现在新标签页的网址消息聊天框中
要做到能接收到另一个标签页发送的消息,还需要继续配置启动通道层CHANNEL_LAYERS
pip3 install -i https://pypi.douban.com/simple channels-redis==4.0.0
- # settings.py
- CHANNEL_LAYERS = {
- "default": {
- "BACKEND": "channels_redis.core.RedisChannelLayer",
- "CONFIG": {
- "hosts": [("127.0.0.1", 6379)],
- },
- },
- }
$ 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'}
将以下代码替换之前chat/consumers.py里的代码
- # 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}))
此时启动django项目,浏览器打开两个标签页,都进入到http://127.0.0.1:8000/chat/myroom/下,再次发送消息,另一个标签页就能接收到啦~
到目前为止,一个基本的全功能聊天服务器就完成了
我们之前写的consumers.py里的代码都是同步的。同步使用很方便,因为他们可以调用常规的同步I/O函数 例如那些不需要编写特殊代码就可以访问Django模型。然而异步消费者可以提供更高级别的性能,因为它们在处理请求时不需要创建额外的线程。
所以我们再次重写chat/consumers.py里的代码,用以下代码替换之前写的同步代码
- # 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}))
这个新的代码是ChatConsumer非常类似于原来的代码,有以下区别:
ChatConsumer
现在继承自 AsyncWebsocketConsumer
,而不是 WebsocketConsumer
。async def
而不仅仅是 def
。await
用于调用执行I/O的异步函数。当
在通道层调用方法时,不再需要async_to_sync。此时再次启动django项目,浏览器打开两个标签页,都进入到http://127.0.0.1:8000/chat/myroom/下,再次发送消息,这时聊天服务器是完全异步的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。