zoukankan      html  css  js  c++  java
  • Django-Channels使用和部署

    本文链接:https://blog.csdn.net/sinat_41292836/article/details/107173795

    Django-Channels作用

    在Django部署的时候,通常使用的都是WSGI(Web Server Gateway Interface)既通用服务网关接口,该协议仅用来处理 Http 请求,更多关于WSGI的说明请参见廖雪峰博客

    当网址需要加入 WebSocket 功能时,WSGI 将不再满足我们的需求,此时我们需要使用ASGI既异步服务网关接口,该协议能够用来处理多种通用协议类型,包括HTTP、HTTP2 和 WebSocket,更多关于 ASGI 的说明请参见此处

    ASGI 由 Django 团队提出,为了解决在一个网络框架里(如 Django)同时处理 HTTP、HTTP2、WebSocket 协议。为此,Django 团队开发了 Django Channels 插件,为 Django 带来了 ASGI 能力。

    在 ASGI 中,将一个网络请求划分成三个处理层面,最前面的一层,interface server(协议处理服务器),负责对请求协议进行解析,并将不同的协议分发到不同的 Channel(频道);频道属于第二层,通常可以是一个队列系统。频道绑定了第三层的 Consumer(消费者)。

    玩转 ASGI:从零到一实现一个实时博客

    Django-Channels使用

    本文基于Django==2.1,channels==2.1.3,channels-redis==2.3.0。

    示例项目RestaurantOrder旨在实现一个基于WebSocket的聊天室,在Channels 2.1.3文档中Tutorial的基础上稍加修改用于微信点餐过程中的多人协作点餐。

    settings.py 加入和 channels 相关的基础设置:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'channels',
        ...
    ]
    
    ASGI_APPLICATION = "RestaurantOrder.routing.application"
    
    # WebSocket
    CHANNEL_LAYERS = {
        'default': {
            'BACKEND': 'channels_redis.core.RedisChannelLayer',
            'CONFIG': {
                "hosts": [('127.0.0.1', 6379)],
            },
        },
    }
    

    wsgi.py 同级目录新增文件 asgi.py:

    """
    ASGI entrypoint. Configures Django and then runs the application
    defined in the ASGI_APPLICATION setting.
    """
    
    import os
    import django
    from channels.routing import get_default_application
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "RestaurantOrder.settings")
    django.setup()
    application = get_default_application()
    

    wsgi.py 同级目录新增文件 routing.py,其作用类型与 urls.py ,用于分发webscoket请求:

    from django.urls import path
    from channels.auth import AuthMiddlewareStack
    from channels.routing import ProtocolTypeRouter, URLRouter
    from table.consumers import TableConsumer
    
    application = ProtocolTypeRouter({
        # Empty for now (http->django views is added by default)
        'websocket': AuthMiddlewareStack(
            URLRouter([
                path('ws/table/<slug:table_id>/', TableConsumer),
            ])
        ),
    })
    

    新增 app 名为 table,在 table 目录下新增 consumers.py:

    from channels.generic.websocket import AsyncJsonWebsocketConsumer
    from table.models import Table
    
    
    class TableConsumer(AsyncJsonWebsocketConsumer):
        table = None
    
        async def connect(self):
            self.table = 'table_{}'.format(self.scope['url_route']['kwargs']['table_id'])
            # Join room group
            await self.channel_layer.group_add(self.table, self.channel_name)
            await self.accept()
    
        async def disconnect(self, close_code):
            # Leave room group
            await self.channel_layer.group_discard(self.table, self.channel_name)
    
        # Receive message from WebSocket
        async def receive_json(self, content, **kwargs):
            # Send message to room group
            await self.channel_layer.group_send(self.table, {'type': 'message', 'message': content})
    
        # Receive message from room group
        async def message(self, event):
            message = event['message']
            # Send message to WebSocket
            await self.send_json(message)
    

    TableConsumer类中的函数依次用于处理连接、断开连接、接收消息和处理对应类型的消息,其中channel_layer.group_send(self.table, {'type': 'message', 'message': content})方法,self.table 参数为当前组的组id, {'type': 'message', 'message': content} 部分分为两部分,type 用于指定该消息的类型,根据消息类型调用不同的函数去处理消息,而 message 内为消息主体。

    table 目录下的 views.py 中新增函数:

    def table(request, table_id):
        return render(request, 'table/table.html', {
            'room_name_json': mark_safe(json.dumps(table_id))
        })
    

    table 函数对应的 urls.py 不再赘述。

    tabletemplates able 目录下新增 table.html:

    <!-- chat/templates/chat/room.html -->
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8"/>
        <title>Chat Room</title>
    </head>
    <body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br/>
    <input id="chat-message-input" type="text" size="100"/><br/>
    <input id="chat-message-submit" type="button" value="Send"/>
    </body>
    <script>
        var roomName = {{ room_name_json }};
    
        var chatSocket = new WebSocket('ws://' + window.location.host + '/ws/table/' + roomName + '/');
    
        chatSocket.onmessage = function (e) {
            var data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (JSON.stringify(data) + '
    ');
        };
    
        chatSocket.onclose = function (e) {
            console.error('Chat socket closed unexpectedly');
        };
    
        document.querySelector('#chat-message-input').focus();
        document.querySelector('#chat-message-input').onkeyup = function (e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#chat-message-submit').click();
            }
        };
    
        document.querySelector('#chat-message-submit').onclick = function (e) {
            var messageInputDom = document.querySelector('#chat-message-input');
            var message = messageInputDom.value;
            chatSocket.send(JSON.stringify(message));
    
            messageInputDom.value = '';
        };
    </script>
    </html>
    

    最终效果:

    Django-Channels部署

    在官方文档中推荐Djaogo-Channelshttp部分和websocket部分均使用daphne进行部署,该方法参见DjangoChannels Docs

    本文使用的方法为使用Nginx代理,将http部分请求发送给uwsgi进行处理,将websocket部分请求发送给daphne进行处理。uwsgidaphhe均使用supervisord进行控制。

    需要注意的是,由于Nginx无法识别http请求和websocket请求,需要通过路由来区分是哪种协议。我使用的方法是规定所有的websocket的路由均以/ws开头(如: ws://www.example/ws/table/table_id/),这样就可以让Nginx将所有以/ws开头的请求全部转发给daphne进行处理。

    Nginxdaphne进行通信时,有http socketfile socket两种通信方式,推荐使用后一种file socket的方式,在这里列出两种通信方式的部署代码。

    • http socket方式

      nginx.conf:

      upstream restaurant_order {
          server unix:///django/RestaurantOrder/restaurant_order.sock;
      }
      
      server {
          listen 8000;
          server_name 114.116.25.246; # substitute your machine's IP address or FQDN
          charset utf-8;
          client_max_body_size 75M;
      
          location /media {
              alias /django/RestaurantOrder/media;
          }
          location /static {
              alias /django/RestaurantOrder/static;
          }
      
          access_log /django/RestaurantOrder/log/access.log;
          error_log /django/RestaurantOrder/log/error.log;
      
          location / {
              uwsgi_pass restaurant_order;
              include /django/RestaurantOrder/uwsgi_params;
          }
      
          location /ws {
              proxy_pass http://127.0.0.1:8001;
      
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
      
              proxy_redirect     off;
              proxy_set_header   Host $host;
              proxy_set_header   X-Real-IP $remote_addr;
              proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header   X-Forwarded-Host $server_name;
              proxy_read_timeout  36000s;
              proxy_send_timeout  36000s;
          }
      }
      

      supervisord.conf:

      [program:restaurant_order_service]
      command=uwsgi --ini /django/RestaurantOrder/restaurant_order_uwsgi.ini
      directory=/django/RestaurantOrder
      stdout_logfile=/django/RestaurantOrder/log/uwsgi_out.log
      stderr_logfile=/django/RestaurantOrder/log/uwsgi_err.log
      autostart=true
      autorestart=true
      user=root
      startsecs=10
      
      
      [program:restaurant_order_websocket]
      command=/django/RestaurantOrder/environment/bin/daphne -b 0.0.0.0 -p 8001 RestaurantOrder.asgi:application
      directory=/django/RestaurantOrder
      stdout_logfile=/django/RestaurantOrder/log/websocket_out.log
      stderr_logfile=/django/RestaurantOrder/log/websocket_err.log
      autostart=true
      autorestart=true
      user=root
      startsecs=10
      
    • file socket方式

      区别于http socket的为2处,1是nginx.conf中的新增upstream websocket,并在location /ws中设置proxy_pass http://websocket;,需要注意此处的http://前缀不可省略;2是daphne的启动方式改为daphne -u /django/RestaurantOrder/websocket.sock RestaurantOrder.asgi:application

      nginx.conf:

      upstream restaurant_order {
          server unix:///django/RestaurantOrder/restaurant_order.sock;
      }
      
      upstream websocket {
          server unix:///django/RestaurantOrder/websocket.sock;
      }
      
      server {
          listen 8000;
          server_name 114.116.25.246; # substitute your machine's IP address or FQDN
          charset utf-8;
          client_max_body_size 75M;
      
          location /media {
              alias /django/RestaurantOrder/media;
          }
          location /static {
              alias /django/RestaurantOrder/static;
          }
      
          access_log /django/RestaurantOrder/log/access.log;
          error_log /django/RestaurantOrder/log/error.log;
      
          location / {
              uwsgi_pass restaurant_order;
              include /django/RestaurantOrder/uwsgi_params;
          }
      
          location /ws {
              proxy_pass http://websocket;
      
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
      
              proxy_redirect     off;
              proxy_set_header   Host $host;
              proxy_set_header   X-Real-IP $remote_addr;
              proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header   X-Forwarded-Host $server_name;
              proxy_read_timeout  36000s;
              proxy_send_timeout  36000s;
          }
      }
      

      supervisord.conf:

      [program:restaurant_order_service]
      command=uwsgi --ini /django/RestaurantOrder/restaurant_order_uwsgi.ini
      directory=/django/RestaurantOrder
      stdout_logfile=/django/RestaurantOrder/log/uwsgi_out.log
      stderr_logfile=/django/RestaurantOrder/log/uwsgi_err.log
      autostart=true
      autorestart=true
      user=root
      startsecs=10
      
      
      [program:restaurant_order_websocket]
      command=/django/RestaurantOrder/environment/bin/daphne -u /django/RestaurantOrder/websocket.sock RestaurantOrder.asgi:application
      directory=/django/RestaurantOrder
      stdout_logfile=/django/RestaurantOrder/log/websocket_out.log
      stderr_logfile=/django/RestaurantOrder/log/websocket_err.log
      autostart=true
      autorestart=true
      user=root
      startsecs=10
  • 相关阅读:
    ueditor 编译出错
    C# HttpWebRequest向远程地址Post文件
    C# HttpWebRequest请求远程地址获取返回消息
    windows server 2008 R2 Enterprise 防火墙开启允许远程桌面登录
    .Net C# 泛型序列化和反序列化JavaScriptSerializer
    两种方法比较两个字符串的不同
    JSP九大内置对象及四个作用域
    聚沙成塔
    maven环境变量配置
    C3P0和DBCP的区别
  • 原文地址:https://www.cnblogs.com/feifeifeisir/p/13743833.html
Copyright © 2011-2022 走看看