【问题标题】:Python Tornado - How to Implement Long-Polling Server to Read from a QueuePython Tornado - 如何实现长轮询服务器以从队列中读取
【发布时间】:2017-01-01 19:31:59
【问题描述】:

我正在尝试构建一个 Web 服务器来通过 AJAX 收集“命令”,然后通过长轮询将命令分发给客户端。

目标是有人将一些数据发布到 /add-command。

另一个客户端实现了一个长轮询客户端,点击 /poll 等待命令执行。

我认为队列是用于保存等待注意的命令的正确数据结构。我希望命令基本上立即分发给任何长轮询客户端,但如果当前没有客户端轮询,则保留。

这是我的 python 脚本。

import os
import time
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.gen
import Queue
import multiprocessing.pool
import mysql.connector
import urlparse
import uuid
import json

_commandQueue = Queue.Queue()
_commandPollInterval = 0.2
_commandPollTimeout = 10

class HomeHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("home.htm")

class AddCommandHandler(tornado.web.RequestHandler):
    def post(self):
        d = urlparse.parse_qs(self.request.body)
        _commandQueue.put(d)
        self.write(str(True))

class PollHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        self.write("start")
        d = 1
        d = yield self.getCommand()
        self.write(str(d))
        self.write("end")
        self.finish()
    @tornado.gen.coroutine
    def getCommand(self):
        start = time.time()
        while (time.time() - start) < _commandPollTimeout * 1000:
            if not _commandQueue.empty:
                return _commandQueue.get()
            else:
                time.sleep(_commandPollInterval)
        return None 

def main():
    application = tornado.web.Application(
        [
            (r"/", HomeHandler),
            (r"/add-command", AddCommandHandler),
            (r"/poll", PollHandler),
        ], 
        debug=True, 
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
    )
    tornado.httpserver.HTTPServer(application).listen(int(os.environ.get("PORT", 5000)))
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

AddCommandHandler 可以很好地将项目放入_commandQueue

PollHandler 请求刚刚超时。如果我调用PollHandler,它似乎锁定了_commandQueue,我无法从中获取或获取。

我怀疑我需要加入队列,但我似乎无法在代码中找到合适的时间。

更新——感谢答案,这是我的最终代码

import os
import time
import datetime
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.queues
import urlparse
import json

_commandQueue = tornado.queues.Queue()
_commandPollInterval = 0.2
_commandPollTimeout = 10

class HomeHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("home.htm")

class AddCommandHandler(tornado.web.RequestHandler):
    def get(self):
        cmd = urlparse.parse_qs(self.request.body)
        _commandQueue.put(cmd)
        self.write(str(cmd))
    def post(self):
        cmd = urlparse.parse_qs(self.request.body)
        _commandQueue.put(cmd)
        self.write(str(cmd))

class PollHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        cmd = yield self.getCommand()
        self.write(str(cmd))
    @tornado.gen.coroutine
    def getCommand(self):
        try:
            cmd = yield _commandQueue.get(
                timeout=datetime.timedelta(seconds=_commandPollTimeout)
            )
            raise tornado.gen.Return(cmd)
        except tornado.gen.TimeoutError:
            raise tornado.gen.Return()

def main():
    application = tornado.web.Application(
        [
            (r"/", HomeHandler),
            (r"/add-command", AddCommandHandler),
            (r"/poll", PollHandler),
        ], 
        debug=True, 
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
    )
    tornado.httpserver.HTTPServer(application).listen(int(os.environ.get("PORT", 5000)))
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

【问题讨论】:

    标签: python tornado


    【解决方案1】:

    在异步模型中你应该省略阻塞操作,time.sleep 在你的代码中是邪恶的。此外,我认为最好的方法是使用龙卷风的(在异步接口中)队列 - tornado.queue.Queue 并使用异步获取:

    import datetime
    import tornado.gen
    import tornado.queues
    
    _commandQueue = tornado.queues.Queue()
    
    
        # ...rest of the code ...
    
        @tornado.gen.coroutine
        def getCommand(self):
            try:
                # wait for queue item if cannot obtain in timeout raise exception
                cmd = yield _commandQueue.get(
                    timeout=datetime.timedelta(seconds=_commandPollTimeout)
                )
                return cmd
            except tornado.gen.Timeout:
                return None
    

    注意:模块 tornado.queues si 自 Tornado 4.x 起可用,如果您使用较旧的模块,Toro 会有所帮助。

    【讨论】:

    • 这为我指明了正确的方向。这里有几个小的更正。它应该是 tornado.queues.Queue。你也不能从协程返回,所以我不得不提出 tornado.gen.Return(cmd)。非常感谢,我被卡住了。
    • 我已根据您的评论进行了更新。 return 自 python 3.2 起在协程(生成器函数)中有效。
    【解决方案2】:

    不能在侦听器中使用 sleep,因为它会阻止从输入流中读取。 time.sleep(_commandPollInterval)。你应该使用的是yield gen.sleep(_commandPollInterval)

    【讨论】:

      猜你喜欢
      • 2012-12-10
      • 2012-06-10
      • 2020-04-16
      • 2017-05-16
      • 2013-05-25
      • 1970-01-01
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多