• 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
            )
        ),
    })
    
  • 相关阅读:
    c/c++ const
    Lucene2.9.1使用小结(同样适用于Lucene 3.0 )
    java 对properties 文件的写操作
    oracle 建表序列插入值
    jxl 读取2003 excel 示例
    HttpClient 的使用
    小故事
    iText 导出word 经典实现
    使用dom4j 解析xml
    lucene 在项目中的使用
  • 原文地址:https://www.cnblogs.com/qianniao2122/p/14440084.html
Copyright © 2020-2023  润新知