提问者:小点点

PythonSocket.IO客户端,用于向TornadIO2服务器发送广播消息


我正在构建一个实时Web应用程序。我希望能够从我的python应用程序的服务器端实现发送广播消息。

这是设置:

  • 客户端上的socketio. js
  • TornadIO2服务器作为Socket.IO服务器
  • 服务器端的python(Django框架)

我可以成功地从客户端向服务器发送socket.io消息。服务器处理这些并可以发送响应。在下文中,我将描述我是如何做到的。

首先,我们需要定义一个处理socket.io事件的Connection:

class BaseConnection(tornadio2.SocketConnection):
    def on_message(self, message):
        pass

    # will be run if client uses socket.emit('connect', username)
    @event
    def connect(self, username):
        # send answer to client which will be handled by socket.on('log', function)
        self.emit('log', 'hello ' + username)

启动服务器由Django管理自定义方法完成:

class Command(BaseCommand):
    args = ''
    help = 'Starts the TornadIO2 server for handling socket.io connections'

    def handle(self, *args, **kwargs):
        autoreload.main(self.run, args, kwargs)

    def run(self, *args, **kwargs):
        port = settings.SOCKETIO_PORT

        router = tornadio2.TornadioRouter(BaseConnection)

        application = tornado.web.Application(
            router.urls,
            socket_io_port = port
        )

        print 'Starting socket.io server on port %s' % port
        server = SocketServer(application)

很好,服务器现在运行。让我们添加客户端代码:

<script type="text/javascript">    
    var sio = io.connect('localhost:9000');

    sio.on('connect', function(data) {
        console.log('connected');
        sio.emit('connect', '{{ user.username }}');
    });

    sio.on('log', function(data) {
        console.log("log: " + data);
    });
</script>

显然,{{user. username}}将被当前登录用户的用户名替换,在本例中用户名为“alp”。

现在,每次刷新页面时,控制台输出为:

connected
log: hello alp

因此,调用消息和发送响应是可行的。但现在到了棘手的部分。

响应“hello alp”仅发送给socket.io消息的调用者。我想向所有连接的客户端广播一条消息,以便在新用户加入该方时实时通知他们(例如在聊天应用程序中)。

所以,以下是我的问题:

>

  • 如何向所有连接的客户端发送广播消息?

    如何向在特定频道上订阅的多个连接客户端发送广播消息?

    如何在我的python代码中的任何地方(在BaseConnection类之外)发送广播消息?这需要某种Python的Socket.IO客户端,还是TornadIO2内置的?

    所有这些广播都应该以可靠的方式进行,所以我想websocket是最好的选择。但是我对所有好的解决方案都持开放态度。


  • 共3个答案

    匿名用户

    我最近在类似的设置上编写了一个非常相似的应用程序,所以我确实有几个见解。

    做你需要的事情的正确方法是有一个pub-sub后端。使用简单的ConnectionHandler只能做这么多。最终,处理类级别的连接集开始变得丑陋(更不用说有缺陷)。

    理想情况下,您会希望使用像Redis这样的东西,并将异步绑定到tornado(查看Brukva)。这样您就不必将客户端注册到特定频道——Redis开箱即用。

    本质上,你有这样的东西:

    class ConnectionHandler(SockJSConnection):
        def __init__(self, *args, **kwargs):
            super(ConnectionHandler, self).__init__(*args, **kwargs)
            self.client = brukva.Client()
            self.client.connect()
            self.client.subscribe('some_channel')
    
        def on_open(self, info):
            self.client.listen(self.on_chan_message)
    
        def on_message(self, msg):
            # this is a message broadcast from the client
            # handle it as necessary (this implementation ignores them)
            pass
    
        def on_chan_message(self, msg):
            # this is a message received from redis
            # send it to the client
            self.send(msg.body)
    
        def on_close(self):
            self.client.unsubscribe('text_stream')
            self.client.disconnect()
    

    请注意,我使用了sockjs-tornado,我发现它比socket.io稳定得多。

    无论如何,一旦你有了这种设置,从任何其他客户端(例如Django,在你的例子中)发送消息就像打开Redis连接一样简单(redis-py是一个安全的赌注)并发布消息:

    import redis
    r = redis.Redis()
    r.publish('text_channel', 'oh hai!')
    

    这个答案相当长,所以我额外努力,写了一篇博客:http://blog.y3xz.com/blog/2012/06/08/a-modern-python-stack-for-a-real-time-web-application/

    匿名用户

    我写在这里,因为很难写在评论部分。您可以在示例目录中查看tornadoio2的示例,您可以在其中找到聊天的实现,并且:

    class ChatConnection(tornadio2.conn.SocketConnection):
        # Class level variable
        participants = set()
    
        def on_open(self, info):
            self.send("Welcome from the server.")
            self.participants.add(self)
    
        def on_message(self, message):
            # Pong message back
            for p in self.participants:
                p.send(message)
    

    如您所见,他们实现了参与者设置))

    匿名用户

    如果您已经在使用django,为什么不看看gevent-socketio。