Django Channels、WS协议及同步与异步详解

CONTENTS

    • 1. 同步与异步
    • 2. WebSocket
    • 3. 在JavaScript中使用WebSocket
    • 4. Django Channels

1. 同步与异步

在 Django 中,同步和异步主要涉及到请求处理的方式。这两种方式的主要区别在于它们如何处理多个并发请求:

  • 同步(Synchronous):在同步模式下,Django 会为每个请求创建一个单独的线程或进程。这意味着,如果一个请求正在等待响应(例如,等待数据库查询返回结果),那么整个线程或进程将被阻塞,直到响应返回。这可能会导致资源的浪费,因为在等待期间,线程或进程不能做其他任何事情。
  • 异步(Asynchronous):与同步模式不同,异步模式允许单个线程或进程同时处理多个请求。当一个请求需要等待响应时(例如,等待数据库查询返回结果),线程或进程可以切换到另一个请求,继续执行其他任务,而不是被阻塞。这样可以更有效地利用系统资源,提高并发处理能力。

Django 3.1 版本开始引入了对异步视图和中间件的支持,这意味着你可以编写异步的视图函数,这些函数可以使用 Python 的 asyncawait 关键字进行定义。这使得 Django 可以更好地处理 I/O 密集型任务,如 HTTP 请求、数据库操作和文件读写等。

然而需要注意的是,并非所有的 Django 组件都支持异步操作。例如,Django 的 ORM(对象关系映射)目前仍然是同步的,这意味着你不能在异步视图或中间件中直接使用它。如果你需要在异步代码中执行数据库操作,你需要使用 Django 提供的 sync_to_asyncasync_to_sync 函数来确保数据库操作在同步环境中执行。

总的来说,同步和异步各有优势和适用场景。对于 CPU 密集型任务,同步模式可能更合适;而对于 I/O 密集型任务,异步模式可能会带来更好的性能。在实际开发中,你可能需要根据应用的具体需求和性能要求来选择使用同步还是异步。

(1)同步代码样例

现在我们来看一些同步与异步的样例,首先是同步视图,在下面这个例子中,当请求到达 sync_view 时,Django 将等待视图函数完成后才会处理下一个请求:

from django.http import HttpResponse
from django.shortcuts import renderdef sync_view(request):  # 这是一个同步视图return HttpResponse('Hello, this is a synchronous view!')

下面这个例子在视图中同步地从数据库获取所有的博客对象,然后将它们传递给模板:

from django.shortcuts import render
from .models import Blogdef sync_view(request):blogs = Blog.objects.all()  # 获取所有的博客对象return render(request, 'blog/index.html', {'blogs': blogs})

(2)异步代码样例

在下面这个例子中,async_view 是一个异步视图。当请求到达这个视图时,Django 可以在等待 asyncio.sleep(1) 完成时处理其他请求:

import asyncio
from django.http import HttpResponseasync def async_view(request):  # 这是一个异步视图await asyncio.sleep(1)return HttpResponse('Hello, this is an asynchronous view!')

请注意,要使用异步视图,你需要确保你的 Django 项目正在运行在支持异步的 ASGI 服务器上,而不是传统的 WSGI 服务器。此外,你的中间件和任何你在视图中调用的代码也必须支持异步。否则,你可能会遇到问题。如果你的代码库主要是同步的,那么最好坚持使用同步视图。如果你正在编写新的、主要使用 Python 的异步库的代码,那么异步视图可能会很有用。请记住,混合使用同步和异步代码可以很复杂,需要谨慎对待。

再来看下面的例子,我们创建了一个新的 get_blogs 异步函数,它使用 run_in_executor 方法在一个单独的线程中运行数据库查询。这允许 Django 在等待数据库查询完成时处理其他请求:

import asyncio
from django.http import JsonResponse
from .models import Blogasync def async_view(request):  # 异步获取所有的博客对象blogs = await get_blogs()return JsonResponse({'blogs': list(blogs.values())})async def get_blogs():loop = asyncio.get_event_loop()blogs = await loop.run_in_executor(None, Blog.objects.all)return blogs

请注意,虽然这个示例展示了如何在 Django 视图中使用异步代码操作数据库,但是 Django 的数据库层目前还不支持原生的异步操作。因此,在实践中,你可能需要使用像 asgiref.sync_to_async 这样的工具来安全地在异步视图中执行同步数据库操作。同时,你也需要确保你的数据库驱动程序和数据库服务器能够处理并发连接。否则,你可能会遇到性能问题或错误。如果你不确定如何正确地使用异步代码,那么最好使用同步视图和同步数据库操作。

2. WebSocket

WebSocket 是一种用于在 Web 和移动应用程序之间进行实时通信的新标准。WebSocket 设计为在 Web 浏览器和 Web 服务器之间实现,但也可以由客户端或服务器应用程序使用。WebSocket 是一种提供单个 TCP 连接上的全双工通信通道的协议,可以实现服务器和客户端之间的实时交互。

WebSocket 与 HTTP 不同,其主要区别如下:

  • 通信方式:HTTP 是单向的,客户端发送请求,服务器发送响应。而 WebSocket 是双向的,在客户端-服务器通信的场景中使用的全双工协议,即客户端和服务器可以同时发送和接收数据。
  • 连接:HTTP 每次请求都需要重新建立连接,而 WebSocket 使用长连接实现数据实时推送。一旦通信链接建立和连接打开后,消息交换将以双向模式进行,客户端-服务器之间的连接会持久存在。
  • 数据传输:HTTP 协议中的数据传输是文本格式的,而 WebSocket 可以传输文本和二进制数据。
  • 性能:由于 HTTP 的每次请求都需要建立连接和断开连接,而 WebSocket 可以在一次连接上进行多次通信,因此 WebSocket 在性能上比 HTTP 有优势。
  • 应用场景:HTTP 主要用于客户端和服务器之间的请求和响应,如浏览器请求网页和服务器返回网页的 HTML 文件。WebSocket 可以实现双向通信,常常用于实时通信场景。
  • 协议头:HTTP 协议头的大小从200字节到2KB不等,常见大小是700-800字节。而 WebSocket 协议头相对较小,这使得其在高频率、小数据量的通信场景下更有优势。
  • 状态:HTTP 是无状态协议,而 WebSocket 是有状态协议。这意味着客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止。

两种协议都位于 OSI 模型的第七层,并依赖于第四层的 TCP。尽管它们是不同的,但 RFC 6455 指出,WebSocket 旨在通过 HTTP 端口443和80工作,并支持 HTTP 代理和中介,从而使其与 HTTP 兼容。为了实现兼容性,WebSocket 握手使用 HTTP Upgrade 头从 HTTP 协议切换到 WebSocket 协议。

WebSocket 协议使得 Web 浏览器(或其他客户端应用程序)和 Web 服务器之间可以在不需要客户端请求的情况下发送内容,以及在保持连接打开的同时传递消息。这样,客户端和服务器之间可以进行双向持续的对话。通信通常是通过 TCP 端口号443(或80,如果是非安全连接)进行的,这对于使用防火墙阻止非 Web Internet 连接的环境是有利的。

在 Django 中实现 WebSocket,你可以选择使用 channels 或者 dwebsocket。但是,channels 被更广泛地使用,因为它可以完美地集成到 Django 的生态系统中。

3. 在JavaScript中使用WebSocket

(1)创建 WebSocket 对象

let ws = new WebSocket('ws://localhost:8888');  // 如果是安全连接则地址为wss://...

(2)连接成功时的回调函数

当 WebSocket 连接成功时,onopen 事件会被触发。你可以在这个函数中发送消息到服务器:

ws.onopen = function(params) {console.log('客户端连接成功');ws.send('hello');  // 向服务器发送消息
};

(3)从服务器接收信息时的回调函数

当从服务器接收到信息时,onmessage 事件会被触发。你可以在这个函数中处理接收到的数据:

ws.onmessage = function(e) {console.log('收到服务器响应', e.data);
};

(4)连接关闭时的回调函数

当连接关闭后,onclose 事件会被触发。你可以在这个函数中处理连接关闭后的逻辑:

ws.onclose = function(e) {console.log("关闭客户端连接");
};

(5)连接失败时的回调函数

ws.onerror = function(e) {console.log("连接失败");
};

(6)监听窗口关闭事件

当窗口关闭时,主动去关闭 WebSocket 连接,防止连接还没断开就关闭窗口,这样服务端会抛异常:

window.onbeforeunload = function() {ws.close();
}

4. Django Channels

Django Channels 是一个开源框架,它扩展了 Django 的功能,使得 Django 不仅可以处理 HTTP,还可以处理需要长时间连接的协议,如 WebSocket、MQTT(消息队列遥测传输)、聊天协议、广播等实时应用。

Channels 允许 Django 项目支持“长连接”,它用 ASGI 替换了 Django 的默认 WSGI。ASGI(Asynchronous Server Gateway Interface)为异步 Python Web 服务器和应用程序提供了一个接口,同时支持 WSGI 提供的所有功能。

Channels 保留了 Django 的同步行为,并添加了一层异步协议,允许用户编写完全同步、异步或两者混合的视图(Views)。Django Channels 提供了一种通信系统,叫做 Channel Layer,它可以让多个 Consumer 实例之间互相通信,以及与外部 Django 程序实现互通。

  • Channel Layer 主要包括两种抽象概念:Channel 和 Group。Channel 是一个发送消息的通道,每个 Channel 都有一个名称,拥有这个名称的人都可以往 Channel 里面发送消息。Group 是多个 Channel 的集合,每个 Group 都有一个名称,拥有这个名称的人都可以往这个 Group 里添加/删除 Channel,也可以往 Group 里发送消息。Group 内的所有 Channel 都可以收到,但是不能给 Group 内的具体某个 Channel 发送消息。使用 Django Channels 可以实现一些实时通讯的功能,如在线聊天室、游戏、通知等。
  • Consumer 是 Channels 的基本单位,相当于 Django 的视图,它是一个事件驱动的类,可以处理不同类型的事件,如连接、断开、接收消息等,支持同步和异步应用程序。

现在我们来看一下 Django Channels 的样例,首先需要安装 Channels 和 Channels Redis:

pip install channels channels_redis

然后需要在你的项目设置中(settings.py 文件)配置 CHANNEL_LAYERS 需要添加 channels 到你的 INSTALLED_APPS 列表,并设置 ASGI_APPLICATIONCHANNEL_LAYERS。例如:

INSTALLED_APPS = [ 'channels',  # 添加此行'game.apps.GameConfig','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',
]ASGI_APPLICATION = 'djangoapp.asgi.application'  # 'djangoapp'为你的项目名CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer","CONFIG": {"hosts": [("127.0.0.1", 6379)],},},
}

在这个例子中,我们假设你正在本地运行一个 Redis 服务器,它监听在6379端口。如果你的 Redis 服务器在其他地方或者使用了不同的端口,你需要更新 hosts 设置。

接下来你需要在 Django App 的目录下创建一个路由文件 routing.py,作用相当于 HTTP 的 urls,并在其中定义你的 WebSocket 路由。我们先创建出来:

from django.urls import pathwebsocket_urlpatterns = [
]

此外,你还需要运行一个兼容的 ASGI 服务器,如 Daphne 或 Uvicorn。我们安装 Daphne:

pip install daphne

输入 daphne 命令查看是否可用,如果不可用说明应该是没有配置环境变量,按如下方式修改环境变量(需要重启系统):

sudo vim /etc/environment
在 PATH='xxx' 后面添加 ':/home/<用户名>/.local/bin'
即: 'xxx:/home/<用户名>/.local/bin'

为了在 Django 项目中使用 Daphne,你需要确保你的项目已经配置为使用 ASGI 而不是 WSGI。这通常意味着你需要在你的项目中创建一个 asgi.py 文件,并在你的设置文件中设置 ASGI_APPLICATION 变量(之前已经设置好了)。

现在我们配置 djangoapp/djangoapp/asgi.py 文件:

import osfrom channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from game.routing import websocket_urlpatternsos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoapp.settings')  # 'djangoapp'为你的项目名application = ProtocolTypeRouter({"http": get_asgi_application(),"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})

现在我们在 Django App 目录下创建 consumers.py

from channels.generic.websocket import AsyncWebsocketConsumer
import jsonclass ChatConsumer(AsyncWebsocketConsumer):async def connect(self):self.room_name = self.scope['url_route']['kwargs']['room_name']self.room_group_name = 'chat_%s' % self.room_name# 加入房间await self.channel_layer.group_add(self.room_group_name,self.channel_name)await self.accept()async def disconnect(self, close_code):# 离开房间await self.channel_layer.group_discard(self.room_group_name,self.channel_name)# 接收来自WebSocket的消息async def receive(self, text_data):text_data_json = json.loads(text_data)message = text_data_json['message']# 发送消息到房间组await self.channel_layer.group_send(self.room_group_name,{'type': 'chat_message','message': message})# 接收来自房间组的消息async def chat_message(self, event):message = event['message']# 发送消息到WebSocketawait self.send(text_data=json.dumps({'message': message}))

在这个例子中,我们创建了一个 ChatConsumer 类,它是一个异步的 WebSocket Consumer,这个类继承自 AsyncWebsocketConsumer,这是 Django Channels 提供的一个基础类。

其中的主要函数说明如下:

  • async def connect(self):当一个 WebSocket 连接打开时,这个方法会被调用。在这个方法中,我们从 URL 路由中获取房间名,并将其保存在 self.room_name 中。然后,我们创建一个房间组名,并将其保存在 self.room_group_name 中。然后,我们将当前的 Channel 添加到房间组中。最后,我们接受 WebSocket 连接。
  • async def disconnect(self, close_code):当一个 WebSocket 连接关闭时,这个方法会被调用。在这个方法中,我们将当前的 Channel 从房间组中移除。
  • async def receive(self, text_data):当从 WebSocket 接收到消息时,这个方法会被调用。在这个方法中,我们首先将接收到的文本数据解析为 JSON,然后我们从 JSON 数据中获取消息,并将其发送到房间组中。
  • async def chat_message(self, event):这是一个自定义的事件处理器方法,当从房间组接收到类型为 chat_message 的事件时,这个方法会被调用。在这个方法中,我们首先从事件中获取消息,然后我们将消息发送回 WebSocket。

总的来说,这段代码的功能为:当用户连接到 WebSocket 时,他们会被添加到一个名为 chat_{room_name} 的组中。当他们发送消息时,这个消息会被广播到他们所在的组中的所有其他用户。当他们接收到组中的消息时,这个消息会被发送回他们的 WebSocket,即实现了一个简单的聊天室功能。

最后我们定义一下 Consumer 的路由,以下代码将 URL 路径 ws/chat/{room_name}/ 映射到我们的 ChatConsumer。这意味着当用户连接到这个路径的 WebSocket 时,他们会被连接到聊天室:

from django.urls import re_path
from .consumers import ChatConsumerwebsocket_urlpatterns = [re_path(r'ws/chat/(?P<room_name>\w+)/$', ChatConsumer.as_asgi()),
]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

删除的通话记录也能找回!如何iPhone很早以前的通话记录

是否搜索过忘记保存的拨号号码?是的!我们都经历过,这可能真的很烦人,尤其是当你的iPhone决定隐藏你的通话记录时。但别担心,在这篇文章中,我将向你展示如何使用各种方法查看一个月前iPhone上的通话历史记录。因此,让我们来了解和发现如何在iPhone上查看你过去的通话记录…

什么是指标体系,怎么搭建一套完整的指标体系?(附PDF素材)

什么是指标体系&#xff0c;怎么搭建一套完整的指标体系&#xff1f;数字化转型过程中&#xff0c;这个问题一直困扰着数据分析师。主要体现在&#xff1a; 各部门根据业务需求&#xff0c;都有一部分量化指标&#xff0c;但不够全面&#xff0c;对企业整体数据分析应用能力提…

算法练习12——跳跃游戏

LeetCode 55 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 贪…

Python日志模块dome

import os import logbook from logbook.more import ColorizedStderrHandler from functools import wrapscur_path os.path.dirname(os.path.dirname(__file__)) # 当前目录的上级目录 log_path os.path.join(cur_path, logs) # 文件夹拼接 if not os.path.exists(log_pa…

MySQL锁概述

数据库锁是一种机制&#xff0c;用于管理并发访问数据库的方式。当多个用户或事务同时访问数据库时&#xff0c;可能会导致数据不一致或冲突的问题。数据库锁的作用是确保数据的一致性和完整性&#xff0c;同时允许多个用户并发地访问数据库。 需要注意的是&#xff0c;加锁是消…

ajax同步与异步,json-serve的安装与使用,node.js的下载

20.ajax json 轻量级的数据格式做配置文件网络传输 xml 重量级的数据格式 可扩展标记语言做配置文件网络传输 现在目前主流就是大量采用json做网络传输数据格式 1.ajax的概念: 与服务器进行’通信’的一种技术,能够实现异步的刷新页面 **同步:**按照顺序一步步的执行,容易造…

VMware和Debian下载

文章目录 ⭐️写在前面的话⭐️一、VMware二、Debain三、建立虚拟机&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680;&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680; ⭐️写在前面的话⭐️ CSDN主页&#xff1a;程序员好冰 目前在…

多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测

多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测 目录 多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果…

使用PyTorch处理多维特征输入的完美指南

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

C++ 模板同时使用默认参数和偏特化

C 模板同时使用默认参数和偏特化 正常的偏特化都很简单&#xff0c;但是如果和默认参数碰到一起就会复杂一点点 先讲答案&#xff08;按照顺序&#xff09;&#xff1a; 如果显示指定了&#xff0c;首先看显示指定的如果没显示指定&#xff0c;就找是否有默认参数的版本如果都…

Element Plus阻止 el-dropdown、el-switch等冒泡事件

最近做vue3项目&#xff0c;使用Element Plus,又遇到坑了&#xff01; 问题点&#xff1a;组件中遇到事件冒泡问题了&#xff0c;el-checkbox 中 change事件要求阻止冒泡&#xff0c;如下代码中要求点击checkbox时不调用li标签的show方法 <li click"show()">…

STM32物联网基于ZigBee智能家居控制系统

实践制作DIY- GC0169-ZigBee智能家居 一、功能说明&#xff1a; 基于STM32单片机设计-ZigBee智能家居 二、功能介绍&#xff1a; 1个主机显示板&#xff1a;STM32F103C最小系统ZigBee无线模块OLED显示器 语音识别模块多个按键ESP8266-WIFI模块&#xff08;仅WIFI版本有&…

Spring Cloud Pipelines 入门实践

文章目录 1. 前言2. Spring Cloud Pipelines 是做什么的2.1. 预定义的流程2.2. 集成测试和契约测试2.3.部署策略 4. Spring Cloud Pipelines的使用示例4.1. 创建一个Spring Boot应用4.2. 将代码托管到GitHub仓库4.3. 添加Spring Cloud Pipelines依赖4.4. 配置Spring Cloud Pipe…

生成Android证书

前提&#xff1a;确保本机安装好了java1.8 第一步 打开CMD keytool -genkey -alias AAAA -keyalg RSA -keysize 2048 -validity 36500 -keystore D:\mygitee\keyStore\szxApp.jksD:\mygitee\keyStore\szxApp.jks是证书要生成的位置 szxApp.jks 是证书名称 D:\mygitee\keySto…

volatile为什么无法保证原子性

假设定义 volatile int i 0; 现在2个线程同时 i&#xff0c;为什么数据还可能会出错&#xff1f;一起来看下图&#xff0c;虽然volatile的机制是&#xff1a;如果volatile修饰的变量有修改&#xff0c;那么会将变更内容写回主内存&#xff0c;同时让其他线程工作内存的该变量缓…

Pytorvh之Vision Transformer图像分类

文章目录 前言一、Transformer1.Transformer概览2.Self-Attention3.Multi-head Attention4.Position-wise Feed-Forward Networks(位置前馈网络)5.残差连接和层归一化6.Positional Encodings(位置编码) 二、Vision Transformer1.Vision Transformer概览2.Embedding层结构&#…

C++中resize和reserve

1.reserve(n)对capacity操作 capacity < n : 扩容capacity > n : 不操作 2.resize(n, m)对size操作 size < n : size增加到n 增加的值为msize > n : size减小到ncapacity < n : 先增大容量至n 再增大size至n 增加的值为m

计算机网络 | OSI 参考模型

计算机网络 | OSI 参考模型 计算机网络 | OSI 参考模型应用层表示层会话层传输层网络层数据链路层物理层 参考视频&#xff1a;王道计算机考研 计算机网络 参考书&#xff1a;《2022年计算机网络考研复习指导》 计算机网络 | OSI 参考模型 OSI 参考模型自下而上分为7层&…

Text-to-SQL小白入门(八)RLAIF论文:AI代替人类反馈的强化学习

学习RLAIF论文前&#xff0c;可以先学习一下基于人类反馈的强化学习RLHF&#xff0c;相关的微调方法&#xff08;比如强化学习系列RLHF、RRHF、RLTF、RRTF&#xff09;的论文、数据集、代码等汇总都可以参考GitHub项目&#xff1a;GitHub - eosphoros-ai/Awesome-Text2SQL: Cur…

mysql5.7获取json数组中的某个对象

前言 表中的一个字段类型是字符串&#xff0c;存的是一个对象数据。 现在要根据对象中的某个属性&#xff0c;获取到整个对象信息。 如果是mysql8&#xff0c;则可以使用JSON_TABLE。 示例&#xff1a;https://blog.csdn.net/weixin_44071721/article/details/123347229 sele…