在另一个消费者的一个Django Channels消费者中断开循环

时间:2017-07-28 09:13:17

标签: python django websocket django-channels

我正在使用支持websockets的Django和Django频道构建web-app。

当用户点击浏览器中的按钮时,websocket会将数据发送到我的服务器,服务器上的消费者开始每秒向客户端发送一次消息(循环播放)。

我想创建另一个按钮来停止此数据发送过程。当用户点击这个新按钮时,websocket会向服务器发送另一个数据,服务器上的消费者必须以某种方式停止前一个消费者的循环。此外,当客户端断开连接时,我将要求它停止循环。

我觉得我想使用全局变量。但Django Channels文档指出,他们强烈建议不要使用全局变量,因为他们希望保持应用程序网络透明(不要理解这一点)。

我尝试过使用频道会话。我让第二个消费者更新了渠道会话中的价值,但渠道会话价值并没有在第一个消费者中更新。

这是简化的代码。 浏览器:

var socket = new WebSocket("ws://" + window.location.host + "/socket/");
$('#button1').on('click', function() { 
    socket.send(JSON.stringify({action: 'start_getting_values'}))
});
$('#button2').on('click', function() { 
    socket.send(JSON.stringify({action: 'stop_getting_values'}))
});

服务器上的消费者:

@channel_session
def ws_message(message):
    text = json.loads(message.content['text'])

    if text['action'] == 'start_getting_values':
        while True:
            # Getting some data here
            # ...
            message.reply_channel.send({"text": some_data}, immediately=True)
            time.sleep(1)

    if text['action'] == 'stop_getting_values':
        do_something_to_stop_the_loop_above()

1 个答案:

答案 0 :(得分:3)

好吧,在我联系了Django Channels开发人员之后,我设法自己解决了这个问题。

在消费者中制作循环的方法很糟糕,因为一旦消费者运行的次数等于运行此消费者的所有工作人员的线程数量,它将阻止该网站。

所以我的方法如下:一旦消费者获得'start_getting_values'信号,它会将当前回复频道添加到一个组,并在连接到的Redis服务器上增加值(我使用Redis作为频道层后端,但它可以工作任何其他后端)。

它增加了什么价值?在Redis上,我有一个关键的哈希对象类型'组'。此键的每个键表示通道中的一个组,值表示该组中的回复通道数量。

然后我创建了一个新的python文件,我连接到同一个Redis服务器。在这个文件中,我运行无限循环,从Redis的关键'组'加载dict。然后我循环遍历此dict中的每个键(每个键代表通道组名称)并将数据广播到每个具有非零值的组。当我运行此文件时,它作为单独的进程运行,因此不会阻止消费者端的任何内容。

要停止向用户广播,当我从他那里得到适当的信号时,我只是将他从群组中删除,并减少相应的Redis值。

消费者代码:

import redis

redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

@channel_session_user
def ws_message(message):

    text = json.loads(message.content['text'])

    if text['stream'] == 'start_getting_values':
        value_id = text['value_id']
        redis_client.hincrby('redis_some_key', value_id, 1)
        Group(value_id).add(message.reply_channel)
        channel_session['value_id'] = value_id
        return 0

    if text['stream'] == 'stop_getting_values':
        if message.channel_session['value_id'] != '':
            value_id = message.channel_session['value_id']
            Group(value_id).discard(message.reply_channel)

            l = redis_client.lock(name='del_lock')
            val = redis_client.hincrby('redis_some_key', value_id, -1)
            if (val <= 0):
                redis_client.hdel('redis_some_key', value_id)
            l.release()
        return 0

单独的python文件:

import time
import redis
from threading import Thread
import asgi_redis


redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
cl = asgi_redis.RedisChannelLayer()

def some_action(value_id):

    # getting some data based on value_id
    # ....

    cl.send_group(value_id, {
        "text": some_data,
    })


while True:
    value_ids = redis_client.hgetall('redis_some_key')

    ths = []
    for b_value_id in value_ids.keys():
        value_id = b_value_id.decode("utf-8")
        ths.append(Thread(target=some_action, args=(value_id,)))

    for th in ths:
        th.start()
    for th in ths:
        th.join()


    time.sleep(1)