Python, Twisted, Django中reactor.run()造成问题

2 投票
3 回答
2772 浏览
提问于 2025-04-16 09:03

我有一个用Django做的网页应用程序。同时,我在同一台机器上运行着一个用twisted写的拼写服务器,这个服务器在localhost:8090上运行。我的想法是,当用户进行某个操作时,请求会发送到Django,然后Django会连接到这个twisted服务器,服务器再把数据发送回Django。最后,Django会把这些数据放到一个HTML模板中,返回给用户。

现在我遇到了一个问题。在我的Django应用中,当请求到达时,我会创建一个简单的twisted客户端来连接本地运行的twisted服务器。

...
        factory = Spell_Factory(query) 
        reactor.connectTCP(AS_SERVER_HOST, AS_SERVER_PORT, factory)
        reactor.run(installSignalHandlers=0)
        print factory.results
...

但是,reactor.run()这个部分出现了问题。因为它是一个事件循环。下次Django执行同样的代码时,我就无法连接到服务器了。这个问题应该怎么解决呢?

3 个回答

1

在你从Twisted服务器获取到结果或者遇到错误/超时时,应该停止反应器(reactor)。所以每次在Django请求中需要查询Twisted服务器时,你都应该先启动反应器,然后再把它停掉。不过,Twisted库不支持这样做——反应器是不能重新启动的。这里有几个可能的解决办法:

  • 可以为Twisted反应器使用一个单独的线程,但你需要在支持长时间运行线程的服务器上部署你的Django应用(我不知道有哪些这样的服务器,不过你可以很容易地自己写一个 :-))。

  • 不使用Twisted来实现客户端协议,直接用标准库中的socket模块就可以了。

3

reactor.run() 这个命令在你的整个程序中只需要调用一次。不要把它想成“启动我这个请求”,而是要理解成“启动整个 Twisted”。

在后台线程中运行 reactor 是一种解决方法;这样你的 Django 应用就可以在里面使用 blockingCallFromThread,就像使用任何阻塞的 API 一样。不过,你需要和你的 WSGI 容器稍微配合一下,因为你需要确保这个后台的 Twisted 线程在合适的时机启动和停止(也就是在你的解释器初始化和销毁的时候)。

你也可以直接用 Twisted 作为你的 WSGI 容器,这样就不需要特别去启动或停止什么;blockingCallFromThread 会立即生效。可以查看 twistd web --wsgi 的命令行帮助来了解更多。

4

上面两个回答都是对的。不过,考虑到你已经实现了一个拼写 服务器,那么就把它当作一个服务来运行吧。你可以先在同一台机器上作为一个独立的进程运行它,地址是 localhost:PORT。现在看起来你已经有了一个非常简单的二进制协议接口,你可以用标准库里的 socket 接口在阻塞模式下实现一个同样简单的 Python 客户端。

不过,我建议你试试 twisted.web,为它提供一个简单的网页接口。你可以使用 JSON 来序列化和反序列化数据,这在 Django 中得到了很好的支持。这里有一个非常简单的例子:

import json
from twisted.web import server, resource
from twisted.python import log

class Root(resource.Resource):
    def getChild(self, path, request):
        # represents / on your web interface
        return self

class WebInterface(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        log.msg('GOT a GET request.')
        # read request.args if you need to process query args
        # ... call some internal service and get output ...
        return json.dumps(output)

class SpellingSite(server.Site):
    def __init__(self, *args, **kwargs):
        self.root = Root()
        server.Site.__init__(self, self.root, **kwargs)
        self.root.putChild('spell', WebInterface())

要运行它,你可以使用以下的骨架 .tac 文件:

from twisted.application import service, internet

site = SpellingSite()
application = service.Application('WebSpell')
# attach the service to its parent application
service_collection = service.IServiceCollection(application)
internet.TCPServer(PORT, site).setServiceParent(service_collection)

把你的服务作为一个独立的服务运行,这样将来如果需要的话,你可以在另一台机器上运行它。而且,提供一个网页接口也让你可以更容易地通过反向代理负载均衡器来进行横向扩展。

撰写回答