简体   繁体   English

Django频道/达芙妮的Websocket超时

[英]Websocket timeout in Django Channels / Daphne

Short question version: what am I doing wrong in my Daphne config, or my Consumer code, or my client code? 简短的问题版本:在我的Daphne配置,我的消费者代码或我的客户端代码中,我做错了什么?

channels==1.1.8
daphne==1.3.0
Django==1.11.7

Details below: 详情如下:


I am trying to keep a persistent Websocket connection open using Django Channels and the Daphne interface server. 我试图使用Django Channels和Daphne接口服务器保持持久的Websocket连接打开。 I am launching Daphne with mostly default arguments: daphne -b 0.0.0.0 -p 8000 my_app.asgi:channel_layer . 我正在使用大多数默认参数启动Daphne: daphne -b 0.0.0.0 -p 8000 my_app.asgi:channel_layer

I am seeing the connections closing after some idle time in the browser, shortly over 20 seconds. 我看到连接在浏览器中的一些空闲时间后关闭,很快超过20秒。 The CloseEvent sent with the disconnect has a code value of 1006 (Abnormal Closure), no reason set, and wasClean set to false. 与断开连接发送的CloseEventcode值为1006 (异常关闭),没有设置reason ,并且wasClean设置为false。 This should be the server closing the connection without sending an explicit close frame. 应该是服务器关闭连接而不发送显式关闭帧。

The Daphne CLI has --ping-interval and --ping-timeout flags with default values of 20 and 30 seconds, respectively. Daphne CLI具有--ping-interval--ping-timeout标志,默认值分别为20秒和30秒。 This is documented as "The number of seconds a WebSocket must be idle before a keepalive ping is sent," for the former, and "The number of seconds before a WebSocket is closed if no response to a keepalive ping," for the latter. 对于前者,这被记录为“在发送保持活动ping之前WebSocket必须空闲的秒数”,对于后者,记录为“如果对keepalive ping没有响应则关闭WebSocket之前的秒数”。 I read this as Daphne will wait until a WebSocket has been idle for 20 seconds to send a ping, and will close the Websocket if no response is received 30 seconds later. 我读到这个,因为Daphne将等待WebSocket空闲20秒发送ping,如果30秒后没有收到响应,将关闭Websocket。 What I am seeing instead is connections getting closed after being 20 seconds idle. 我所看到的是连接在空闲20秒后关闭。 (Across three attempts with defaults, closed after 20081ms, 20026ms, and 20032ms) (默认情况下三次尝试,在20081ms,20026ms和20032ms之后关闭)

If I change the server to launch with daphne -b 0.0.0.0 -p 8000 --ping-interval 10 --ping-timeout 60 my_app.asgi:channel_layer , the connections still close, around 20 seconds idle time. 如果我将服务器更改为使用daphne -b 0.0.0.0 -p 8000 --ping-interval 10 --ping-timeout 60 my_app.asgi:channel_layer ,则连接仍然关闭,大约20秒空闲时间。 (After three attempts with updated pings, closed after 19892ms, 20011ms, 19956ms) (经过三次尝试更新ping后,在19892ms之后关闭,20011ms,19956ms)

Code below: 代码如下:


consumer.py : consumer.py

import logging

from channels import Group
from channels.generic.websockets import JsonWebsocketConsumer

from my_app import utilities

logger = logging.getLogger(__name__)

class DemoConsumer(JsonWebsocketConsumer):
    """
    Consumer echos the incoming message to all connected Websockets,
    and attaches the username to the outgoing message.
    """
    channel_session = True
    http_user_and_session = True

    @classmethod
    def decode_json(cls, text):
        return utilities.JSONDecoder.loads(text)

    @classmethod
    def encode_json(cls, content):
        return utilities.JSONEncoder.dumps(content)

    def connection_groups(self, **kwargs):
        return ['demo']

    def connect(self, message, **kwargs):
        super(DemoConsumer, self).connect(message, **kwargs)
        logger.info('Connected to DemoConsumer')

    def disconnect(self, message, **kwargs):
        super(DemoConsumer, self).disconnect(message, **kwargs)
        logger.info('Disconnected from DemoConsumer')

    def receive(self, content, **kwargs):
        super(DemoConsumer, self).receive(content, **kwargs)
        content['user'] = self.message.user.username
        # echo back content to all groups
        for group in self.connection_groups():
            self.group_send(group, content)

routing.py : routing.py

from channels.routing import route

from . import consumers

channel_routing = [
    consumers.DemoConsumer.as_route(path=r'^/demo/'),
]

demo.js : demo.js

// Tracks the cursor and sends position via a Websocket
// Listens for updated cursor positions and moves an icon to that location
$(function () {
  var socket = new WebSocket('ws://' + window.location.host + '/demo/');
  var icon;
  var moveTimer = null;
  var position = {x: null, y: null};
  var openTime = null;
  var lastTime = null;
  function sendPosition() {
    if (socket.readyState === socket.OPEN) {
      console.log('Sending ' + position.x + ', ' + position.y);
      socket.send(JSON.stringify(position));
      lastTime = Date.now();
    } else {
      console.log('Socket is closed');
    }
    // sending at-most 20Hz
    setTimeout(function () { moveTimer = null; }, 50);
  };
  socket.onopen = function (e) {
    var box = $('#websocket_box');
    icon = $('<div class="pointer_icon"></div>').insertAfter(box);
    box.on('mousemove', function (me) {
      // some browsers will generate these events much closer together
      // rather than overwhelm the server, batch them up and send at a reasonable rate
      if (moveTimer === null) {
        moveTimer = setTimeout(sendPosition, 0);
      }
      position.x = me.offsetX;
      position.y = me.offsetY;
    });
    openTime = lastTime = Date.now();
  };
  socket.onclose = function (e) {
    console.log("!!! CLOSING !!! " + e.code + " " + e.reason + " --" + e.wasClean);
    console.log('Time since open: ' + (Date.now() - openTime) + 'ms');
    console.log('Time since last: ' + (Date.now() - lastTime) + 'ms');
    icon.remove();
  };
  socket.onmessage = function (e) {
    var msg, box_offset;
    console.log(e);
    msg = JSON.parse(e.data);
    box_offset = $('#websocket_box').offset();
    if (msg && Number.isFinite(msg.x) && Number.isFinite(msg.y)) {
      console.log((msg.x + box_offset.left) + ', ' + (msg.y + box_offset.top));
      icon.offset({
        left: msg.x + box_offset.left,
        top: msg.y + box_offset.top
      }).text(msg.user || '');
    }
  };
});

asgi.py : asgi.py

import os
from channels.asgi import get_channel_layer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")

channel_layer = get_channel_layer()

settings.py : settings.py

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgi_redis.RedisChannelLayer',
        'ROUTING': 'main.routing.channel_routing',
        'CONFIG': {
            'hosts': [
                'redis://redis:6379/2',
            ],
            'symmetric_encryption_keys': [
                SECRET_KEY,
            ],
        }
    }
}

The underlying problem turned out to be the nginx proxy in front of the interface server. 根本问题是接口服务器前面的nginx代理。 The proxy was set to proxy_read_timeout 20s; 代理设置为proxy_read_timeout 20s; . If there were keepalive pings generated from the server, these were not getting counted toward the upstream read timeout. 如果从服务器生成了keepalive ping,则这些ping不会计入上游读取超时。 Increasing this timeout to a larger value allows the Websocket to stay open longer. 将此超时增加到更大的值允许Websocket更长时间保持打开状态。 I kept proxy_connect_timeout and proxy_send_timeout at 20s . 我保持proxy_connect_timeoutproxy_send_timeout20s

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM