如何在Python(Tornado)中websocket循环之外运行函数

32

我正在尝试设置一个公共Twitter流的小例子,使用websockets连接。 这是我的websocket.py,并且它可以工作。

我想知道的是:如何在WSHandler类“外部”与websocket进行交互(即不仅在接收websocket.js消息时回答)? 假设我想要在此脚本中运行一些其他函数,每五秒钟发布“hello!”并将其发送到websocket(浏览器),而无需任何来自客户端的交互。 我该怎么做呢?

所以这是一个初学者的基础问题,我想知道如何处理如下类。 任何方向上的任何指针都将不胜感激!

import os.path
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

# websocket
class FaviconHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/static/favicon.ico')

class WebHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("websockets.html")

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hi, client: connection is made ...")

    def on_message(self, message):
        print 'message received: \"%s\"' % message
        self.write_message("Echo: \"" + message + "\"")
        if (message == "green"):
            self.write_message("green!")

    def on_close(self):
        print 'connection closed'



handlers = [
    (r"/favicon.ico", FaviconHandler),
    (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': 'static'}),
    (r'/', WebHandler),
    (r'/ws', WSHandler),
]

settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "static"),
)

application = tornado.web.Application(handlers, **settings)

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

21
你可以调用一个
IOLoop.add_timeout(deadline, callback)
调用指定截止时间的回调函数(一次性的,但可以重新安排),或者如果有更周期性的任务,则使用 tornado.ioloop.PeriodicCallback
请参见:http://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_timeout 更新:一些示例。
import datetime

def test():
    print "scheduled event fired"
...

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    main_loop = tornado.ioloop.IOLoop.instance()
    # Schedule event (5 seconds from now)
    main_loop.add_timeout(datetime.timedelta(seconds=5), test)
    # Start main loop
    main_loop.start()

在5秒后它调用test()函数。

更新 2:

import os.path
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

# websocket
class FaviconHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/static/favicon.ico')

class WebHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("websockets.html")

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hi, client: connection is made ...")
        tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=5), self.test)

    def on_message(self, message):
        print 'message received: \"%s\"' % message
        self.write_message("Echo: \"" + message + "\"")
        if (message == "green"):
            self.write_message("green!")

    def on_close(self):
        print 'connection closed'

    def test(self):
        self.write_message("scheduled!")

handlers = [
    (r"/favicon.ico", FaviconHandler),
    (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': 'static'}),
    (r'/', WebHandler),
    (r'/ws', WSHandler),
]

settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "static"),
)

application = tornado.web.Application(handlers, **settings)

import datetime

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

谢谢。但是在哪里呢?我尝试调用WSHandler,但是得到了“必须使用WSHandler实例调用...”的错误信息。 - knutole
1
我添加了一些使用add_timeout()的示例。 - pr0gg3d
你是否知道如何从test()中调用WSHandler?这样我就可以从那里调用self.write_message()了。 - knutole
2
IOLoop.instance() 是一个单例(在典型的使用中)。您可以多次调用 instance(),并返回相同的单例。 - pr0gg3d
1
请查看第二个更新。它会在连接后的5秒钟内发送一条预定消息。 - pr0gg3d
显示剩余2条评论

17

我遇到了类似的问题。这是我的解决方案。希望对那里的某个人有所帮助。

wss = []
class wsHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'Online'
        if self not in wss:
            wss.append(self)

    def on_close(self):
        print 'Offline'
        if self in wss:
            wss.remove(self)

def wsSend(message):
    for ws in wss:
        ws.write_message(message)

要向您的Websockets发送消息,只需使用以下内容:

wsSend(message)

wsSend 更新

我偶尔会遇到使用wsSend时出现异常的情况。为了解决这个问题,我稍微修改了代码如下:

def wsSend(message):
    for ws in wss:
        if not ws.ws_connection.stream.socket:
            print "Web socket does not exist anymore!!!"
            wss.remove(ws)
        else:
            ws.write_message(message)

2
对Tornado还很陌生,所以……您能否为这个非常有前途的答案添加必要的样板代码呢?因为我想看看它如何与(如果我理解正确)多个websocket处理程序一起工作。也就是说,您能否给出一个if __name__ == "__main__"块的示例,该块将初始化IOLoop(s?)和handler(s?)。乍一看似乎非常优雅。 - Thomas Browne
Thomas,我很想帮助你,但是已经有一段时间了,而且我也不是Tornado的专家。但是据我所记,Tornado有出色的文档,你可以随时浏览源代码。祝你好运! - Barmaley

3
一种实现此功能的方法是使用发布/订阅模块。
这意味着您的连接将订阅它,而不是为每个单独的连接设置超时,您只需在指定时间后设置一个超时来发布
最常用的可能是redis。还有一些专门针对tornado的模块:例如toredisbrükva
当然,这对于简单的页面可能并不必要,但在扩展方面非常好,并且一旦设置好了,也很容易维护/扩展。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接