使用Tornado和Prototype的异步COMET查询

9 投票
4 回答
6425 浏览
提问于 2025-04-15 19:37

我正在尝试使用Tornado和JS Prototype库来写一个简单的网页应用。这样,客户端就可以在服务器上执行一些需要很长时间的任务。我希望这个任务能够异步运行,这样其他客户端就可以继续浏览页面并进行一些操作。

这是我目前的代码:

#!/usr/bin/env/ python

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options

import os
import string
from time import sleep
from datetime import datetime

define("port", default=8888, help="run on the given port", type=int)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("templates/index.html", title="::Log watcher::", c_time=datetime.now())

class LongHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.wait_for_smth(callback=self.async_callback(self.on_finish))
        print("Exiting from async.")
        return

    def wait_for_smth(self, callback):
        t=0
        while (t < 10):
            print "Sleeping 2 second, t={0}".format(t)
            sleep(2)
            t += 1
        callback()

    def on_finish(self):
        print ("inside finish")
        self.write("Long running job complete")
        self.finish()



def main():
    tornado.options.parse_command_line()

    settings = {
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
        }

    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/longPolling", LongHandler)
        ], **settings
    )
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

这是服务器部分。它有一个主视图(显示简单的问候语、当前的服务器时间和一个用于ajax查询的链接,这个链接会执行长时间运行的任务)。如果你按下一个按钮,就会执行这个长时间的任务。结果就是服务器会卡住 :( 在这个任务运行的时候,我无法查看任何页面。

这是模板页面:

<html>
<head>
    <title>{{ title }}</title>

    <script type="text/javascript" language="JavaScript" src="{{ static_url("js/prototype.js")}}"></script>


    <script type='text/javascript' language='JavaScript'>
        offset=0
        last_read=0

        function test(){
            new Ajax.Request("http://172.22.22.22:8888/longPolling",
            {
                method:"get",
                asynchronous:true,
                onSuccess: function (transport){
                    alert(transport.responseText);
                }
            })
        }

        
    </script>
</head>
<body>
    Current time is {{c_time}}
    <br>
    <input type="button" value="Test" onclick="test();"/>
</body>
</html>

我哪里做错了?我该如何使用Tornado和Prototype(或者jQuery)来实现长轮询呢?

附注:我看过聊天的例子,但那个太复杂了。我搞不懂它是怎么工作的 :(

补充说明:可以下载完整的 示例

4 个回答

0

我把Tornado的聊天示例改成可以在gevent上运行的版本。你可以在这里查看实时演示,还有这里的解释和源代码

这个版本使用了轻量级的用户级线程(叫做greenlets),在速度和内存使用上和Tornado差不多。不过,代码写起来很简单,你可以在处理函数里直接调用sleep()和urlopen(),而不会阻塞整个程序,还可以启动一些长时间运行的任务,做同样的事情。实际上,这个应用是异步的,底层是用C语言写的事件循环来支持的(叫做libevent)。

你可以在这里阅读介绍

1
function test(){
            new Ajax.Request("http://172.22.22.22:8888/longPolling",
            {
                method:"get",
                asynchronous:true,
                onSuccess: function (transport){
                    alert(transport.responseText);
                }
            })
        }
function test(){
            new Ajax.Request("/longPolling",
            {
                method:"get",
                asynchronous:true,
                onSuccess: function (transport){
                    alert(transport.responseText);
                }
            })
        }

应该是

15

Tornado 是一个单线程的网页服务器。你在 wait_for_smith 方法里的 while 循环会让 Tornado 停下来,无法处理其他请求。

你可以把这个方法改成这样:

def wait_for_smth(self, callback, t=10):
    if t:
        print "Sleeping 2 second, t=%s" % t
        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 2, lambda: self.wait_for_smth(callback, t-1))
    else:
        callback()

你需要在最上面加上 import time,这样才能让它正常工作。

撰写回答