美文网首页Python/Go
django websocket

django websocket

作者: manbug | 来源:发表于2019-03-18 11:28 被阅读0次

    日常的网络通信大多使用http/https, 在这种协议下通信的建立取决于客户端的需要, 那么存不存在一种由服务端主导通信的协议呢

    前言

    做个比喻, 如果说A是服务端, B是客户端, 现在要在A家里吃火锅, 虽然A说你人来就行, 但是B心想总得带点东西过去, 于是去了市场.
    先到了蔬菜店, B想买点菠菜, 但又怕A家里已经有了, 于是给A打电话
    B: "我带点菠菜过去吧?"
    A: "好"
    然后挂断. 过一会儿到了水产区
    B: "我带点虾过去吧?"
    A: "不用"
    ...如此反复多了之后A突然发现自己确实少准备了一些东西, 于是A给主动给B打了电话
    A: "我忘准备蘸料了, 你买点, 然后先别挂掉"
    ...
    A: "再买瓶酒"
    ...
    这就是websocket了

    websocket库

    django当让也提供对websocket的支持, 虽然这似乎不是他更擅长的东西. 我们可以通过channels实现websocket连接

    使用场景

    诸如上述例子的场景都是合适的场景
    举例来说的话比如聊天室, 每个人发送的消息都要实时显示在别人的屏幕上.
    比如说数据监控, 波动状态也要实时的呈现在屏幕上, 而不是依赖于使用者自己刷新.

    django channels 安装/配置

    需要安装channels, asgi_redis, asgiref, channels_redis. 后三个未必都需要装, 记不太清了, 总之安装过程都在channels的使用文档上.
    INSTALL_APPS中需要加上"channels", 需要注意的是因为这是一个list, 是有先后顺序的, 最好把它加在第一个.
    这里我们的channel通过redis实现, 要在settings.py中配置

    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [{"address": (REDIS_HOST, REDIS_PORT), "db": 8, "password": REDIS_PASSWD}]
            },
        },
    }
    

    这里还有点小坑, 官方文档里的hosts不是这种格式, 是"uri"这种模式, 但是如果你在设置redis密码时机智的设置了特殊符号('#$%'这种), 你就会发现redis的uri直接就用不了了, 期间尝试各种方法, 转义什么的也试了都不行, 然后去github上开了个issue, 结果作者说我们是通过aioredis连接的, 你去找他们的文档吧....
    然后就找到了这种方式.
    常规的WSGI不支持websocket, 所以还需要配置ASGI
    ASGI_APPLICATION = 'project.routing.application'
    同wsgi的配置一样, 这是指向project文件夹下routing.py文件的application

    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
            )
        ),
    })
    

    使用

    这里建议大家跟这官方教程的Tutorial走一遍. 有个比较悲剧的地方就是网上可以搜到许多channels使用指南, 大多都是搭个简易聊天室什么的, 然而你用起来可能发现存在各种报错, 因为channels升了2.0之后更改了一些方法, 而那些教程里基本全都是1.x的版本.
    简单说下, 首先startappchat, 假如这里我们没有进行前后端分离, 里面有templates, 两个html:indexroom分别对应首页和某一个聊天室
    新建consumers.py来写websocket方法

    import json
    
    from backend.dao.db_conn import redis_pool_db9
    from backend.service.dashboard import preparing_list
    
    from asgiref.sync import async_to_sync
    from channels.generic.websocket import WebsocketConsumer
    from django.conf import settings
    
    
    # test: 测试用, 聊天室demo
    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']
            username = text_data_json['username']
            if message == "1":
                message = "mark it."
    
            # Send message to room group
            async_to_sync(self.channel_layer.group_send)(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': message,
                    'username': username
                }
            )
    
        # Receive message from room group
        def chat_message(self, event):
            message = event['message']
            username = event['username']
            if message == "1":
                message = "mark it."
    
            # Send message to WebSocket
            self.send(text_data=json.dumps({
                'message': message,
                'username': username
            }))
    

    如上, connectdisconnect含义分别如函数名. 因为是聊天室, 所以同一个聊天室内的人应该消息共享, 用room_group_name来区分所在的频道.
    receivechat_message是对消息的处理. 当一个用户发送消息时, 前端把消息通过websocket发送过来, receive收到消息提取关键内容, 通过chat_message发送给组内的所有连接. 这时保持连接的所有组内人员都会收到这条消息推送, 前端收到推送再显示在屏幕上.
    定义websocket的地址
    类似于djangourl(consumers.py就类似于views.py), 同级新建routing.py

    from django.conf.urls import url
    
    from . import consumers
    
    websocket_urlpatterns = [
        url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
        url(r'^ws/dashboard/daily_left/$', consumers.DailyLeftConsumer),
        url(r'^ws/dashboard/assigning_queue/$', consumers.AssigningQueueConsumer),
    ]
    

    统一用ws/来区分websocket的连接
    剩下常规的页面配置和django一样
    views.py:

    from django.shortcuts import render
    from django.utils.safestring import mark_safe
    import json
    
    
    def index(request):
        return render(request, 'chat/index.html', {})
    
    
    def room(request, room_name):
        return render(request, 'chat/room.html', {
            'room_name_json': mark_safe(json.dumps(room_name))
        })
    

    urls.py:

    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        url(r'^$', views.index, name='index'),
        url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
    ]
    

    前端配置

    注意: 如果网站是http, 连接使用ws, 如果是https要修改成wss

    var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    var chatSocket = new WebSocket(
            {#ws_scheme + '://' + window.location.host +#}
            ws_scheme + '://' + window.location.hostname +
            '/ws/chat/' + roomName + '/');
    

    剩下的自己找资料吧, 笔者对前端了解的不多

    启动

    本地的话runserver就好了, 但是在线上还是得更改启动方式应对高并发.
    传统的uwsgi不支持websocket.
    gunicorn好像可以同时支持websocket, 但是性能不太ok
    这里我们用daphne

    daphne -b 0.0.0.0 -p 8001 mshan.asgi:application --access-log /var/log/daphne.log
    

    k8s配置

    这里需要额外开个服务, 专门负责处理websocket.
    ingress中要配置路由跳转


    ingress2.png

    相关文章

      网友评论

        本文标题:django websocket

        本文链接:https://www.haomeiwen.com/subject/fcvqdqtx.html