当前位置:   article > 正文

django channels(websocket)的使用,实现网页间实时聊天_django 聊天

django 聊天

目录

一、创建项目mysite和应用chat

1. 创建项目mysite

2. 创建应用chat并添加应用到settings.py的INSTALLED_APPS中

3. 添加模板文件

4. 添加视图及路由

5.配置根路由指定chat应用的路由

6. 集成channels

二、实现聊天服务器

1. 创建一个新文件chat/templates/chat/room.html,并添加以下内容

2. 创建视图room及配置路由

3. 配置消费者consumers.py

4. 配置routings.py

5. 再次配置asgi.py

6. 启用通道层CHANNEL_LAYERS

7. 再次配置consumers.py

三、改进:将消费者重写为异步


版本

python==3.7

django==3.2.18

channels==3.0.3
channels-redis==4.0.0

一、创建项目mysite和应用chat

1. 创建项目mysite

使用pycharm创建项目mysite

使用命令创建项目mysite

django-admin startproject mysite

2. 创建应用chat并添加应用到settings.py的INSTALLED_APPS中

 python manage.py startapp chat

  1. # settings.py
  2. INSTALLED_APPS = [
  3. 'django.contrib.admin',
  4. 'django.contrib.auth',
  5. 'django.contrib.contenttypes',
  6. 'django.contrib.sessions',
  7. 'django.contrib.messages',
  8. 'django.contrib.staticfiles',
  9. 'chat',
  10. ]

 

 完成之后项目的目录结构:

3. 添加模板文件

在chat应用下新建templates文件夹,并右键选择Mark Direcory as >> Template Folder,templates的文件夹将变为紫色,并创建index.html文件

 index.html添加以下内容

  1. <!-- chat/templates/chat/index.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8"/>
  6. <title>Chat Rooms</title>
  7. </head>
  8. <body>
  9. What chat room would you like to enter?<br>
  10. <input id="room-name-input" type="text" size="100"><br>
  11. <input id="room-name-submit" type="button" value="Enter">
  12. <script>
  13. document.querySelector('#room-name-input').focus();
  14. document.querySelector('#room-name-input').onkeyup = function(e) {
  15. if (e.keyCode === 13) { // enter, return
  16. document.querySelector('#room-name-submit').click();
  17. }
  18. };
  19. document.querySelector('#room-name-submit').onclick = function(e) {
  20. var roomName = document.querySelector('#room-name-input').value;
  21. window.location.pathname = '/chat/' + roomName + '/';
  22. };
  23. </script>
  24. </body>
  25. </html>

4. 添加视图及路由

  1. # chat/views.py
  2. from django.shortcuts import render
  3. def index(request):
  4. return render(request, "index.html")
  1. # chat/urls.py
  2. from django.urls import path
  3. from . import views
  4. urlpatterns = [
  5. path("", views.index, name="index"),
  6. ]

5.配置根路由指定chat应用的路由

  1. # mysite/urls.py
  2. from django.contrib import admin
  3. from django.urls import include, path
  4. urlpatterns = [
  5. path("chat/", include("chat.urls")),
  6. path("admin/", admin.site.urls),
  7. ]

此时启动django项目,浏览器访问http://127.0.0.1:8000/chat/

 

 这里在输入框中输入字符串后点击Enter会报404的错误,所以我们接下来继续配置

6. 集成channels

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里的代码

  1. import os
  2. from channels.routing import ProtocolTypeRouter
  3. from django.core.asgi import get_asgi_application
  4. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
  5. application = ProtocolTypeRouter(
  6. {
  7. "http": get_asgi_application(),
  8. }
  9. )

c.在settings.py中添加channels应用及配置ASGI_APPLICATION

  1. # mysite/settings.py
  2. INSTALLED_APPS = [
  3. 'django.contrib.admin',
  4. 'django.contrib.auth',
  5. 'django.contrib.contenttypes',
  6. 'django.contrib.sessions',
  7. 'django.contrib.messages',
  8. 'django.contrib.staticfiles',
  9. 'chat',
  10. 'channels'
  11. ]
  12. ASGI_APPLICATION = "mysite.asgi.application"

二、实现聊天服务器

1. 创建一个新文件chat/templates/chat/room.html,并添加以下内容

  1. <!-- chat/templates/chat/room.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8"/>
  6. <title>Chat Room</title>
  7. </head>
  8. <body>
  9. <textarea id="chat-log" cols="100" rows="20"></textarea><br>
  10. <input id="chat-message-input" type="text" size="100"><br>
  11. <input id="chat-message-submit" type="button" value="Send">
  12. {{ room_name|json_script:"room-name" }}
  13. <script>
  14. const roomName = JSON.parse(document.getElementById('room-name').textContent);
  15. const chatSocket = new WebSocket(
  16. 'ws://'
  17. + window.location.host
  18. + '/ws/chat/'
  19. + roomName
  20. + '/'
  21. );
  22. chatSocket.onmessage = function(e) {
  23. const data = JSON.parse(e.data);
  24. document.querySelector('#chat-log').value += (data.message + '\n');
  25. };
  26. chatSocket.onclose = function(e) {
  27. console.error('Chat socket closed unexpectedly');
  28. };
  29. document.querySelector('#chat-message-input').focus();
  30. document.querySelector('#chat-message-input').onkeyup = function(e) {
  31. if (e.keyCode === 13) { // enter, return
  32. document.querySelector('#chat-message-submit').click();
  33. }
  34. };
  35. document.querySelector('#chat-message-submit').onclick = function(e) {
  36. const messageInputDom = document.querySelector('#chat-message-input');
  37. const message = messageInputDom.value;
  38. chatSocket.send(JSON.stringify({
  39. 'message': message
  40. }));
  41. messageInputDom.value = '';
  42. };
  43. </script>
  44. </body>
  45. </html>

2. 创建视图room及配置路由

  1. # chat/views.py
  2. from django.shortcuts import render
  3. def index(request):
  4. return render(request, "chat/index.html")
  5. # 新添加
  6. def room(request, room_name):
  7. return render(request, "room.html", {"room_name": room_name})
  1. # chat/urls.py
  2. from django.urls import path
  3. from . import views
  4. urlpatterns = [
  5. path("", views.index, name="index"),
  6. path("<str:room_name>/", views.room, name="room"), # 新添加
  7. ]

此时启动django项目,浏览器打开控制台输入地址http://127.0.0.1:8000/chat/

输入myroom,并Enter

 输入消息并点击Send没有任何事发生,所以接下来继续配置消费者consumers

3. 配置消费者consumers.py

在chat应用下添新建consumers.py文件,并添加以下内容

  1. # chat/consumers.py
  2. import json
  3. from channels.generic.websocket import WebsocketConsumer
  4. class ChatConsumer(WebsocketConsumer):
  5. def connect(self):
  6. self.accept()
  7. def disconnect(self, close_code):
  8. pass
  9. def receive(self, text_data):
  10. text_data_json = json.loads(text_data)
  11. message = text_data_json["message"]
  12. self.send(text_data=json.dumps({"message": message}))

目录结构

 

4. 配置routings.py

在chat应用下新建routing.py文件,并添加以下内容

  1. # chat/routing.py
  2. from django.urls import re_path
  3. from . import consumers
  4. websocket_urlpatterns = [
  5. re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
  6. ]

5. 再次配置asgi.py

  1. # mysite/asgi.py
  2. import os
  3. from channels.auth import AuthMiddlewareStack
  4. from channels.routing import ProtocolTypeRouter, URLRouter
  5. from channels.security.websocket import AllowedHostsOriginValidator
  6. from django.core.asgi import get_asgi_application
  7. from chat.routing import websocket_urlpatterns
  8. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
  9. # Initialize Django ASGI application early to ensure the AppRegistry
  10. # is populated before importing code that may import ORM models.
  11. django_asgi_app = get_asgi_application()
  12. import chat.routing
  13. application = ProtocolTypeRouter(
  14. {
  15. "http": django_asgi_app,
  16. "websocket": AllowedHostsOriginValidator(
  17. AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
  18. ),
  19. }
  20. )

运行迁移命令

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

 

6. 启用通道层CHANNEL_LAYERS

  • 先安装 channels-redis

pip3 install -i https://pypi.douban.com/simple channels-redis==4.0.0

  • settings.py中添加CHANNEL_LAYERS的配置(请确认在这之前已经安装了redis)
  1. # settings.py
  2. CHANNEL_LAYERS = {
  3. "default": {
  4. "BACKEND": "channels_redis.core.RedisChannelLayer",
  5. "CONFIG": {
  6. "hosts": [("127.0.0.1", 6379)],
  7. },
  8. },
  9. }
  • 确保通道层可以与Redis通信。打开Django shell并运行以下命令:  
$ 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. 再次配置consumers.py

将以下代码替换之前chat/consumers.py里的代码

  1. # chat/consumers.py
  2. import json
  3. from asgiref.sync import async_to_sync
  4. from channels.generic.websocket import WebsocketConsumer
  5. class ChatConsumer(WebsocketConsumer):
  6. def connect(self):
  7. self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
  8. self.room_group_name = f"chat_{self.room_name}"
  9. # Join room group
  10. async_to_sync(self.channel_layer.group_add)(
  11. self.room_group_name, self.channel_name
  12. )
  13. self.accept()
  14. def disconnect(self, close_code):
  15. # Leave room group
  16. async_to_sync(self.channel_layer.group_discard)(
  17. self.room_group_name, self.channel_name
  18. )
  19. # Receive message from WebSocket
  20. def receive(self, text_data):
  21. text_data_json = json.loads(text_data)
  22. message = text_data_json["message"]
  23. # Send message to room group
  24. async_to_sync(self.channel_layer.group_send)(
  25. self.room_group_name, {"type": "chat.message", "message": message}
  26. )
  27. # Receive message from room group
  28. def chat_message(self, event):
  29. message = event["message"]
  30. # Send message to WebSocket
  31. 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里的代码,用以下代码替换之前写的同步代码

  1. # chat/consumers.py
  2. import json
  3. from channels.generic.websocket import AsyncWebsocketConsumer
  4. class ChatConsumer(AsyncWebsocketConsumer):
  5. async def connect(self):
  6. self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
  7. self.room_group_name = f"chat_{self.room_name}"
  8. # Join room group
  9. await self.channel_layer.group_add(self.room_group_name, self.channel_name)
  10. await self.accept()
  11. async def disconnect(self, close_code):
  12. # Leave room group
  13. await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
  14. # Receive message from WebSocket
  15. async def receive(self, text_data):
  16. text_data_json = json.loads(text_data)
  17. message = text_data_json["message"]
  18. # Send message to room group
  19. await self.channel_layer.group_send(
  20. self.room_group_name, {"type": "chat.message", "message": message}
  21. )
  22. # Receive message from room group
  23. async def chat_message(self, event):
  24. message = event["message"]
  25. # Send message to WebSocket
  26. 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/下,再次发送消息,这时聊天服务器是完全异步的

参考: Tutorial — Channels 4.0.0 documentation

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/327504
推荐阅读
相关标签
  

闽ICP备14008679号