zoukankan      html  css  js  c++  java
  • django应用channels实现websocket

    channels实现websocket

    channels

    [官方文档]

    Channels通过Django的同步核心编织异步代码,允许Django项目不仅处理HTTP,还可以处理需要长时间连接的协议 - WebSockets,MQTT,chatbots,业余无线电等等。

    它在保留Django同步和易用性的同时实现了这一点,允许您选择编写代码的方式 - 以Django视图,完全异步或两者混合的方式同步。除此之外,它还提供了与Django的auth系统,会话系统等的集成,使您可以比以往更轻松地将仅HTTP项目扩展到其他协议。

    安装channels

    channels可通过pip直接安装:

    > pip install channels
    # 若遇到twisted安装报错请下载.whl格式安装包手动安装
    

    将channels库添加到INSTALLED_APPS

    INSTALLED_APPS = [
        # ...
        'channels',  # 【channels】(第1步)pip install channels 安装
        # ...
    ]
    

    创建默认路由(主WS路由)

    Channels路由配置类似于Django URLconf,因为当通道服务器接收到HTTP请求时,它告诉通道运行什么代码。 将从一个空路由配置开始。创建一个文件 projectname/routing.py ,并包含以下代码:

    # 【channels】(第2步)设置默认路由在项目创建routing.py文件
    
    from channels.routing import ProtocolTypeRouter
    
    application = ProtocolTypeRouter({
        # Empty for now (http->django views is added by default)
    })
    

    设置执行路由对象(指定routing)

    最后,将ASGI_APPLICATION设置为指向路由对象作为根应用程序,修改 settings.py 文件,添加:

    # 【channels】(第3步)设置为指向路由对象作为根应用程序
    ASGI_APPLICATION = "StarMeow.routing.application"
    

    启动channel layer(后端redis)

    信道层是一种通信系统。它允许多个消费者实例彼此交谈,以及与Django的其他部分交谈。 通道层提供以下抽象: 通道是一个可以将邮件发送到的邮箱。每个频道都有一个名称。任何拥有频道名称的人都可以向频道发送消息。 一组是一组相关的通道。一个组有一个名称。任何具有组名称的人都可以按名称向组添加/删除频道,并向组中的所有频道发送消息。无法枚举特定组中的通道。 每个使用者实例都有一个自动生成的唯一通道名,因此可以通过通道层进行通信。 在我们的聊天应用程序中,我们希望同一个房间中的多个聊天消费者实例相互通信。为此,我们将让每个聊天消费者将其频道添加到一个组,该组的名称基于房间名称。这将允许聊天用户向同一房间内的所有其他聊天用户发送消息。 我们将使用一个使用redis作为后备存储的通道层。要在端口6379上启动Redis服务器,首先系统上安装redis,并启动。

    pip安装channels-redis

    > pip install channels_redis
    

    修改 settings.py 增加配置:

    # 【channels】后端
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": ["redis://:password@127.0.0.1:6379/0"], # redis://{authenticateString}@{HOST}:{PORT}/{DATABASE}
            },
        },
    }
    

    确保channel layer可以与Redis通信。打开Django 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'}
    

    应用下创建 consumer.py (类似django视图)

    使用异步方式

    同步消费者很方便,因为他们可以调用常规的同步I / O函数,例如那些在不编写特殊代码的情况下访问Django模型的函数。 但是,异步使用者可以提供更高级别的性能,因为他们在处理请求时不需要创建其他线程。

    ChatConsumer仅使用异步本机库(通道和通道层),特别是它不访问同步Django模型。 因此,它可以被重写为异步而不会出现复杂情况。

    appname/consumers.py

    # 【channels】创建应用的消费者
    from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
    from asgiref.sync import async_to_sync
    from channels.layers import get_channel_layer
    import json
    
    
    class AsyncConsumer(AsyncWebsocketConsumer):
        async def connect(self):  # 连接时触发
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            self.room_group_name = 'notice_%s' % self.room_name  # 直接从用户指定的房间名称构造Channels组名称,不进行任何引用或转义。
    
            # 将新的连接加入到群组
            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
            )
    
        # Receive message from WebSocket
        async def receive(self, text_data=None, bytes_data=None):  # 接收消息时触发
            text_data_json = json.loads(text_data)
            message = text_data_json['message']
    
            # 信息群发
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'system_message',
                    'message': message
                }
            )
    
        # Receive message from room group
        async def system_message(self, event):
            print(event)
            message = event['message']
    
            # Send message to WebSocket单发消息
            await self.send(text_data=json.dumps({
                'message': message
            }))
    
    
    # 同步方式,仅作示例,不使用
    class SyncConsumer(WebsocketConsumer):
        def connect(self):
            # 从打开到使用者的WebSocket连接的chat/routing.py中的URL路由中获取'room_name'参数。
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            print('WebSocket建立连接:', self.room_name)
            # 直接从用户指定的房间名称构造通道组名称
            self.room_group_name = 'msg_%s' % self.room_name
    
            # 加入房间
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )  # async_to_sync(…)包装器是必需的,因为ChatConsumer是同步WebsocketConsumer,但它调用的是异步通道层方法。(所有通道层方法都是异步的。)
    
            # 接受WebSocket连接。
            self.accept()
            simple_username = self.scope["session"]["session_simple_nick_name"]  # 获取session中的值
    
            async_to_sync(self.channel_layer.group_send)(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': '@{} 已加入房间'.format(simple_username)
                }
            )
    
        def disconnect(self, close_code):
            print('WebSocket关闭连接')
            # 离开房间
            async_to_sync(self.channel_layer.group_discard)(
                self.room_group_name,
                self.channel_name
            )
    
        # 从WebSocket中接收消息
        def receive(self, text_data=None, bytes_data=None):
            print('WebSocket接收消息:', text_data)
            text_data_json = json.loads(text_data)
            message = text_data_json['message']
    
            # 发送消息到房间
            async_to_sync(self.channel_layer.group_send)(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': message
                }
            )
    
        # 从房间中接收消息
        def chat_message(self, event):
            message = event['message']
    
            # 发送消息到WebSocket
            self.send(text_data=json.dumps({
                'message': message
            }))
    
    • 使用异步时继承自AsyncWebsocketConsumer而不是WebsocketConsumer。
    • 所有方法都是async def而不是def。
    • await用于调用执行I / O的异步函数。
    • 在通道层上调用方法时不再需要async_to_sync。

    应用下创建 routing.py

    appname/routing.py

    # 【channels】为应用程序创建一个路由配置,该应用程序具有到消费者的路由
    from django.conf.urls import url
    from assets import consumers
    
    websocket_urlpatterns = [
        # url(r'^ws/msg/(?P<room_name>[^/]+)/$', consumers.SyncConsumer),
        url(r'^ws/msg/(?P<room_name>[^/]+)/$', consumers.AsyncConsumer),
    ]
    

    修改项目下 routing.py (主WS路由)

    projectname/routing.py

    # 【channels】设置默认路由在项目创建routing.py文件
    
    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.auth import AuthMiddlewareStack
    from channels.sessions import SessionMiddlewareStack
    import assets.routing
    
    application = ProtocolTypeRouter({
        # (http->django views is added by default)
        # 【channels】(第6步)添加路由配置指向应用的路由模块
        'websocket': SessionMiddlewareStack(  # 使用Session中间件,可以请求中session的值
            URLRouter(
                assets.routing.websocket_urlpatterns
            )
        ),
    })
    
  • 相关阅读:
    centos7.6 安装与配置 MongoDB yum方式
    MongoDB 介绍
    centos 关闭selinux
    前端 HTML标签属性
    前端 HTML 标签嵌套规则
    前端 HTML 标签分类
    前端 HTML body标签相关内容 常用标签 表单标签 form里面的 input标签介绍
    前端 HTML body标签相关内容 常用标签 表单标签 form 表单控件分类
    前端 HTML form表单标签 select标签 option 下拉框
    POJ 1426
  • 原文地址:https://www.cnblogs.com/qianniao2122/p/14440084.html
Copyright © 2011-2022 走看看