Django---websocket

官方教程: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使用者,它接受所有连接,从其客户端接收消息,并将这些消息回送到同一客户端。
# 11
# 同步的可以使用 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

原文地址:https://www.cnblogs.com/BlueFire-py/p/10043611.html