官方教程:https://channels.readthedocs.io/en/latest/tutorial/part_1.html
官方教程给出了一个聊天室的例子,主要应用了channel_layer,也就是组的概念。
1、简介
不同于HTTP请求,WebSockets协议使用双向直接通信,也就是说不需要客户端发送请求,服务器端就可以向发送数据。
HTTP协议中,只有客户端可以发送请求和接收响应,WebSockets协议中,服务器端可以同时与多个客户端进行通信。我们将使用ws://前缀而不是http://。
注意:一个ws的url可以是一个channel_layer,也可以是一个channel。
也就是说一个url对应一个组(聊天室)或者对于多个用户(在本机不同的端口上进行交流)。
一个组的用户是可以互相交流的,而没有加组则不能交流(各自是各自的)。
组名很重要:在官方的例子中,只要是相同的url就可以进相同的组,当然这是因为房间名称是从url中获取的,这是一个很好的设计模式,由url来区分房间,因为后台虽然可以直接指定组,但是大家都是统一接口组名无法分配。这样一来可以由前端分配。
ModuleNotFoundError: No module named 'win32api' pip install pypiwin32解决不了
直接在命令行里python manage.py runserver可以跳过这个问题
如何在类外使用:只有组才能在组外其他地方进行向组内传消息。
https://channels.readthedocs.io/en/latest/topics/channel_layers.html#using-outside-of-consumers
AsyncHttpConsumer(一个consumer类解决一个ws问题)(消息的类型很重要)
2、安装
pip install channels
3、基本使用(Integrate the Channels library 集成)
没有channel layer的使用,现在实现的是一对一,不能在类外调用
1.让我们首先为Channels创建根路由配置。Channels路由配置类似于Django URLconf,它告诉Channels Channels服务器收到HTTP请求时要运行的代码。
# mysite/routing.py from channels.routing import ProtocolTypeRouter application = ProtocolTypeRouter({ # (http->django views is added by default) })
2.现在将Channels库添加到已安装的应用程序列表中。编辑mysite / settings.py文件并将“channels”添加到INSTALLED_APPS设置。
3.您还需要在根路由配置中指向通道。再次编辑mysite / settings.py文件,并在其底部添加以下内容:
# mysite/settings.py # Channels ASGI_APPLICATION = 'mysite.routing.application'
4.当Django接受HTTP请求时,它会查询根URLconf以查找视图函数,然后调用视图函数来处理请求。类似地,当Channels接受WebSocket连接时,它会查询根路由配置以查找使用者,然后调用使用者的各种函数来处理来自连接的事件。
consumer.py
# chat/consumers.py
from channels.generic.websocket import WebsocketConsumer
import json
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
}))
5.routing.py
# chat/routing.py from django.conf.urls import url from . import consumers websocket_urlpatterns = [ url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer), ]
6.下一步是将根路由配置指向chat.routing模块。在mysite / routing.py中,导入AuthMiddlewareStack,URLRouter和chat.routing;并按以下格式在ProtocolTypeRouter列表中插
入'websocket'键
routing.py
# mysite/routing.py from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import chat.routing application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns ) ), })
此根路由配置指定在与Channels开发服务器建立连接时,ProtocolTypeRouter将首先检查连接类型。如果是WebSocket连接(ws://或wss://),则连接将被提供给AuthMiddlewareStack。
AuthMiddlewareStack将使用对当前经过身份验证的用户的引用来填充连接的范围,类似于Django的AuthenticationMiddleware如何使用当前经过身份验证的用户填充视图函数的请求对象。
然后将连接到URLRouter。URLRouter将根据提供的url模式检查连接的HTTP路径,以将其路由到特定的使用者。让我们验证/ ws / chat / ROOM_NAME /路径的消费者是否正常工作。
7.运行迁移以应用数据库更改(Django的会话框架需要数据库),然后启动Channels开发服务器
4、通道层:Enable a channel layer
通道层是一种通信系统。它允许多个消费者实例相互交谈,并与Django的其他部分交谈。
1.通道是可以发送消息的邮箱。每个频道都有一个名字。拥有通道名称的任何人都可以向通道发送消息。(
2.一组是一组相关的通道。一个组有一个名字。具有组名称的任何人都可以按名称向组添加/删除通道,并向组中的所有通道发送消息。无法枚举特定组中的通道。
每个消费者实例都有一个自动生成的唯一通道名称,因此可以通过通道层进行通信。
在我们的聊天应用程序中,我们希望在同一个房间中有多个ChatConsumer实例相互通信。为此,我们将每个ChatConsumer将其频道添加到名称基于房间名称的组。这将允许ChatConsumers将消息传输到同一房间中的所有其他ChatConsumers。
我们将使用一个使用Redis作为其后备存储的通道层。要在端口6379上启动Redis服务器
1、We need to install channels_redis so that Channels knows how to interface with Redis. Run the following command
pip3 install channels_redis
2、Before we can use a channel layer, we must configure it. Edit the mysite/settings.py
file and add a CHANNEL_LAYERS
setting to the bottom. It should look like:
# mysite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
3、检测安装是否成功
$ 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'}
4、现在我们有了一个通道层,让我们在ChatConsumer中使用它。将以下代码放在chat / consumers.py中
# 同步写法
# chat/consumers.py from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer import json 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 }))
5、类外调用:
https://blog.csdn.net/ros_donggua/article/details/82628381
However most projects will just use a single 'default'
channel layer.。(只能有一个默认通道层)
from channels.layers import get_channel_layer from asgiref.sync import async_to_sync
channel_layer = get_channel_layer()
message="hhhhhh"
async_to_sync(channel_layer.group_send)("chat_123",
{
'type': 'chat_message',
'message': message
})
5、三种方式实现
from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer import time from channels.generic.websocket import AsyncWebsocketConsumer import json # 这是一个同步WebSocket使用者,它接受所有连接,从其客户端接收消息,并将这些消息回送到同一客户端。 # 1 对 1 # 同步的可以使用 while True class ChatConsumer1(WebsocketConsumer): def connect(self): print("begin") self.accept() def disconnect(self, close_code): print("dis") self.close() pass def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] print(message) while True: time.sleep(2) print("后台收到"+message) self.send(text_data=json.dumps({ 'message': message })) # 当用户发布消息时,JavaScript函数将通过WebSocket将消息传输到ChatConsumer。 # ChatConsumer将接收该消息并将其转发到与房间名称对应的组。 # 然后,同一组中的每个ChatConsumer(因此在同一个房间中)将接收来自该组的消息,并通过WebSocket将其转发回JavaScript,并将其附加到聊天日志中。 # 1 对 N class ChatConsumer(WebsocketConsumer): def connect(self): # 在连接的时候加入一个团队 self.room_name = self.scope['url_route']['kwargs']['room_name'] # 从chat / routing.py中的URL路由获取 'room_name' 参数,该参数打开与使用者的WebSocket连接。 # 每个使用者都有一个范围(scope),其中包含有关其连接的信息,特别是包括URL路由中的任何位置或关键字参数以及当前经过身份验证的用户(如果有)。 self.room_group_name = 'chat_%s' % self.room_name # 直接从用户指定的房间名称构造Channels组名称,不进行任何引用或转义。 # 组名只能包含字母,数字,连字符和句点。因此,此示例代码将在具有其他字符的房间名称上失败。 # Join room group async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) # 加入一个团队。 # async_to_sync(...)包装器是必需的,因为ChatConsumer是同步WebsocketConsumer,但它调用异步通道层方法。 # (所有通道层方法都是异步的。)组名仅限于ASCII字母数字,连字符和句点。 # 由于此代码直接从房间名称构造组名称,因此如果房间名称包含在组名称中无效的任何字符,则该名称将失败。 self.accept() # 接受WebSocket连接。 # 如果不在connect()方法中调用accept(),则拒绝并关闭连接。 # 例如,您可能希望拒绝连接,因为请求的用户无权执行请求的操作。如果您选择接受连接,建议将accept()作为connect()中的最后一个操作。 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 })) # 异步实现 # 没有办法实现while True class ChatConsumer(AsyncWebsocketConsumer): # ChatConsumer现在继承自AsyncWebsocketConsumer而不是WebsocketConsumer。 # 所有方法都是异步def而不仅仅是def。 # await用于调用执行I / O的异步函数。 # 在通道层上调用方法时不再需要async_to_sync。 async 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 await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() async def disconnect(self, close_code): await self.close() # 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): # 这是从组里获取到的消息,也就是你url中获取到的 message = event['message'] # Send message to WebSocket # 发送给每个人 # 这个才是真正的发送消息 await self.send(text_data=json.dumps({ 'message': message # 'message': "已发送调试信息,请耐心等待回复" }))
6、前端实现
<script>
# 打开网页就开启socket连接,必须这样,收到消息的函数写道主体中
# 发送函数写道onclick函数中,必须这样
var chatSocket = new WebSocket(
'ws://' + window.location.host + '/ws/chat/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; $("#message").text(message) }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; $("#send").on("click",function(e) { alert("ddd"); chatSocket.send(JSON.stringify({ 'message': "gyx" })); }); </script>
Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: 'd:\envs\aidcs\Lib\site-packages\msgpack\_packer.cp36-win_amd64.pyd'
Consider using the `--user` option or check the permissions.
开着python解释器的任何软件都会影响安装,比如python交互式环境,jupyter-notebook,python manage.py shell