• 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

  • 相关阅读:
    D. Longest Subsequence
    线段树入门HDU_1754
    poj_2503(map映射)
    HDU_4826
    poj_2251
    day 44 单表查询,多表查询
    day43 字段的修改、添加和删除,多表关系(外键),单表详细操作(增删改查)
    day 42 数据库的配置、数据库与表的一些剩余操作、用户操作、数据库表的引擎、数据库的模式、mysql支持的数据类型、约束
    day41 数据库介绍、数据库基本操作
    day 40 线程队列、线程定时器、进程池和线程池、同步与异步、用多线程来写socket服务端与客户端
  • 原文地址:https://www.cnblogs.com/BlueFire-py/p/10043611.html
Copyright © 2020-2023  润新知