Tornado的Websockets:如何从“外部”访问以向客户端发送消息

11 投票
3 回答
9956 浏览
提问于 2025-04-18 05:58

我最近开始学习WebSockets,这是一种可以让服务器向连接的客户端推送数据的技术。因为我用Python来编写各种逻辑,所以我目前关注的是Tornado。下面的代码片段是网上最基础的示例:

import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hello World")

    def on_message(self, message):
        print 'message received %s' % message
        self.write_message('ECHO: ' + message)

    def on_close(self):
    print 'connection closed'


application = tornado.web.Application([
  (r'/ws', WSHandler),
])


if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

这个示例运行得很好。不过,我有点搞不清楚,怎么把这个“集成”到我应用的其他部分。在上面的例子中,WebSocket只是作为对客户端消息的回复来发送数据。我想知道怎么才能从“外部”访问这个WebSocket?比如说,通知所有当前连接的客户端某个事件发生了——而这个事件并不是来自客户端的消息。理想情况下,我希望在我的代码中写一些类似于:

websocket_server.send_to_all_clients("Good news everyone...")

我该怎么做呢?还是说我对WebSockets(或者Tornado)的工作原理完全误解了?谢谢!

3 个回答

0

我的解决方案是:首先在main.py文件里加上"if __name__ == '__main__':"这行代码。然后把main.py导入到websocket模块里,比如可以用import main as MainApp。这样就可以在ws.py/WebSocketHandler的函数里调用'main.py'中的函数了。在Handler里可以这样传递消息:MainApp.function(message)

我不知道这样做算不算优雅,但对我来说是有效的。

另外,还可以创建一个自定义的'config.py'文件(内容类似于:someVar = int(0)),然后在'mainApp.py'里导入它,像这样:import config as cfg。这样你就可以在'main.py'的函数里通过cfg.someVar = newValue来修改变量,这个函数之前是由'ws.py'的Handler调用的。

11

这段内容是基于Hans Then的例子,希望能帮助你理解如何让服务器主动和客户端沟通,而不是让客户端来发起互动。

下面是服务器的代码:

#!/usr/bin/python

import datetime
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web



class WSHandler(tornado.websocket.WebSocketHandler):
    clients = []
    def open(self):
        print 'new connection'
        self.write_message("Hello World")
        WSHandler.clients.append(self)

    def on_message(self, message):
        print 'message received %s' % message
        self.write_message('ECHO: ' + message)

    def on_close(self):
        print 'connection closed'
        WSHandler.clients.remove(self)

    @classmethod
    def write_to_clients(cls):
        print "Writing to clients"
        for client in cls.clients:
            client.write_message("Hi there!")


application = tornado.web.Application([
  (r'/ws', WSHandler),
])


if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=15), WSHandler.write_to_clients)
    tornado.ioloop.IOLoop.instance().start()

我把客户端列表设置成了一个类变量,而不是全局变量。其实我也不介意用全局变量,但因为你对此有顾虑,所以这里提供了另一种方法。

接下来是一个示例客户端:

#!/usr/bin/python

import tornado.websocket
from tornado import gen 

@gen.coroutine
def test_ws():
    client = yield tornado.websocket.websocket_connect("ws://localhost:8888/ws")
    client.write_message("Testing from client")
    msg = yield client.read_message()
    print("msg is %s" % msg)
    msg = yield client.read_message()
    print("msg is %s" % msg)
    msg = yield client.read_message()
    print("msg is %s" % msg)
    client.close()

if __name__ == "__main__":
    tornado.ioloop.IOLoop.instance().run_sync(test_ws)

你可以运行服务器,然后让两个测试客户端连接。当你这样做时,服务器会打印出以下内容:

bennu@daveadmin:~$ ./torn.py 
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed

第一个客户端打印出这个:

bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0

第二个客户端打印出这个:

bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1

为了这个例子的目的,我让服务器在15秒后给客户端发送消息,但你可以根据需要设置触发条件。

12

你需要记录所有连接的客户。所以:

clients = []

def send_to_all_clients(message):
    for client in clients:
        client.write_message(message)

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        send_to_all_clients("new client")
        clients.append(self)

    def on_close(self):
        clients.remove(self)
        send_to_all_clients("removing client")

    def on_message(self, message):
        for client in clients:
            if client != self:
                client.write_message('ECHO: ' + message)

撰写回答