Django/gevent socket.IO 与 redis pubsub,如何安排?

3 投票
1 回答
3035 浏览
提问于 2025-04-17 03:48

我有一个独立的Python脚本,它的功能很简单,就是从Twitter的实时数据接口获取数据。每当收到一条消息时,它会通过Redis的发布/订阅功能,把消息发布到一个叫“tweets”的频道。下面是这个脚本:

def main():
    username = "username"
    password = "password"
    track_list = ["apple", "microsoft", "google"]

    with tweetstream.FilterStream(username, password, track=track_list) as stream:
        for tweet in stream:
            text = tweet["text"]
            user = tweet["user"]["screen_name"]
            message = {"text": text, "user": user}
            db.publish("tweets", message)

if __name__ == '__main__':
    try:
        print "Started..."
        main()
    except KeyboardInterrupt:
        print '\nGoodbye!'

我的服务器端使用的是django-socketio来实现socket.io的功能(这个库是基于gevent-socketio的)。这个库提供了一些方便的装饰器和一个叫broadcast_channel的方法。因为我是在Django框架下做的,所以我把这些代码放在views.py里,只是为了方便导入。以下是我的views.py代码:

def index(request):
    return render_to_response("twitter_app/index.html", {
    }, context_instance=RequestContext(request))

def _listen(socket):
    db = redis.Redis(host="localhost", port=6379, db=0)
    client = db.pubsub()
    client.subscribe("tweets")
    tweets = client.listen()

    while True:
        tweet = tweets.next()
        tweet_data = ast.literal_eval(tweet["data"])
        message = {"text": tweet_data["text"], "user": tweet_data["user"], "type": "tweet"}
        socket.broadcast_channel(message)

@on_subscribe(channel="livestream")
def subscribe(request, socket, context, channel):
    g = Greenlet.spawn(_listen, socket)

客户端的socket.io JavaScript代码则是连接到“livestream”频道,并捕获任何发送到这个频道的消息:

var socket = new io.Socket();
socket.connect();
socket.on('connect', function() {
    socket.subscribe("livestream");
});
socket.on('message', function(data) {
    console.log(data);
});

这个代码明显的问题是,每当有新用户或者新的浏览器窗口打开这个页面时,都会生成一个新的_listen方法,这样每个用户都会订阅到推文,导致每个客户端收到重复的消息。我的问题是,应该把_listen方法放在哪里,才能确保它只创建一次,而不管有多少个客户端?另外,要记住broadcast_channel方法是一个socket实例的方法。

1 个回答

3

问题在于我本来应该用socket.send来发送信息,但我却用了socket.broadcast_channel。

撰写回答