twisted - 交互式队列函数
我刚刚通过这个很不错的教程接触到了twisted,目的是为了做一个聊天应用,但我不太确定怎么把它改成我想要的样子。
假设我突然想让聊天服务器给所有客户端发送一条友好的消息,比如“祝你们长周末愉快!”。也就是说,我想让反应器(reactor)在已经运行的情况下执行某个操作(所以我不能提前安排,或者我不想这样做)。
我想做的事情大概是这样的:
def do_something():
# do something
# setup and run reactor
factory = Factory()
factory.clients = []
factory.protocol = MyServer
reactor.listenTCP(80, factory)
reactor.run() # asynchronously?
# clients connect...
reactor.callLater(0, do_something)
我试过用python的线程,但没成功。我看了这些twisted的例子,但它们的reactor.run()语句都是放在最后,这让我有点困惑。是的,很可能我漏掉了一些基本的东西(这也是我来这里的原因)。
3 个回答
你说过:
假设我突然想给所有客户发送一条友好的消息。
还有:
我想要在服务器上运行时,能够在 Python 中互动地生成事件。
我把这句话翻译成“我希望在我的反应器运行时有一个键盘接口”,接下来给你举个例子。
在 Twisted 中,键盘就像其他输入输出接口一样,你可以用它来进行操作。虽然我提供的例子是针对 Unix/POSIX 类型的平台,但这个思路在其他操作系统上也可以实现。
(免责声明:这个例子有点乱,因为它在设置 tty 的 cbreak 模式。我喜欢这样做来实现互动控制,但这并不是必须的。)
#!/usr/bin/python
import sys # so I can get at stdin
import os # for isatty
import termios, tty # access to posix IO settings
from twisted.internet import reactor
from twisted.internet import stdio # the stdio equiv of listenXXX
from twisted.protocols import basic # for lineReceiver for keyboard
from twisted.internet.protocol import Protocol, ServerFactory
class Cbreaktty(object):
org_termio = None
my_termio = None
def __init__(self, ttyfd):
if(os.isatty(ttyfd)):
self.org_termio = (ttyfd, termios.tcgetattr(ttyfd))
tty.setcbreak(ttyfd)
print ' Set cbreak mode'
self.my_termio = (ttyfd, termios.tcgetattr(ttyfd))
else:
raise IOError #Not something I can set cbreak on!
def retToOrgState(self):
(tty, org) = self.org_termio
print ' Restoring terminal settings'
termios.tcsetattr(tty, termios.TCSANOW, org)
class MyClientConnections(Protocol):
def connectionMade(self):
print "Got new client!"
self.factory.clients.append(self)
def connectionLost(self, reason):
print "Lost a client!"
self.factory.clients.remove(self)
class MyServerFactory(ServerFactory):
protocol = MyClientConnections
def __init__(self):
self.clients = []
def sendToAll(self, message):
for c in self.clients:
c.transport.write(message)
def hello_to_all(self):
self.sendToAll("A friendly message, sent on a whim\n")
print "sending friendly..."
class KeyEater(basic.LineReceiver):
def __init__(self, hello_callback):
self.setRawMode() # Switch from line mode to "however much I got" mode
self.hello_to_all = hello_callback
def rawDataReceived(self, data):
key = str(data).lower()[0]
if key == 's':
self.hello_to_all()
elif key == 'q':
reactor.stop()
else:
print "Press 's' to send a message to all clients, 'q' to shutdown"
def main():
client_connection_factory = MyServerFactory()
try:
termstate = Cbreaktty(sys.stdin.fileno())
except IOError:
sys.stderr.write("Error: " + sys.argv[0] + " only for use on interactive ttys\n")
sys.exit(1)
keyboardobj = KeyEater(client_connection_factory.hello_to_all)
stdio.StandardIO(keyboardobj,sys.stdin.fileno())
reactor.listenTCP(5000, client_connection_factory)
reactor.run()
termstate.retToOrgState()
if __name__ == '__main__':
main()
如果你运行上面的代码(假设你在 Unix/POSIX 系统上),你会得到一个反应器,它既在等待和处理 TCP 连接,又在等待标准输入上的按键。按下 's' 键会向所有连接的客户端发送一条消息。
这是我经常用来异步控制 Twisted 应用的方法,当然这只是众多方法中的一种,其他回答中也提到了很多。
正如JP所说,反应器会对事件做出响应。
- 你可以在Twisted的工厂或协议中启动一个线程,然后通过reactor.callInThread()和reactor.callFromThread()让这个线程发送命令回来。
- 或者你可以设置一个callLater,从一个由callWhenRunning启动的函数中运行,这个函数会检查某个文件或目录,如果找到了就执行一些操作。这个callLater运行的函数也可以设置另一个callLater来进行后续操作。
- 或者,你可以在Twisted工厂中(甚至在协议中)设置一个循环调用。
- 或者,你的聊天服务器可以监听来自客户端的特定字符序列,这些字符会指示服务器执行某个操作(比如向所有登录的客户端发送一条附加消息)。
- 或者你的服务器可以在Twisted内部监控一个IP或Unix的“命令”套接字,观察是否有命令会导致消息被发送。
- 在你的上下文中,事件可以是由Unix信号处理程序发起的动作,不过我记得Twisted内部会做一些处理来拦截信号,所以可能需要一些研究。
我花了大约六个月的时间来理解异步的Twisted思维方式,现在终于开始“明白”了。这需要一些时间,但这是一个值得的旅程。
对某些人来说,学习Twisted就像是80年代那种基于文本的冒险游戏,你大致上是通过实验来摸索,直到发现一个神奇的袋子,吸引到有用的工具。到那时事情就变得简单了。
我发现研究Twisted的源代码非常有帮助,但代码量很大,知道从哪里开始或者在哪里找到东西可能会有挑战。代码通常组织得很好,清晰且相对一致,这点很有帮助。
另一个积极的方面是,很多与Twisted紧密相关的人(Glyph和JP只是其中两个)在这里可以提供帮助。
如果有时间,我计划整理一组示例服务器和客户端,展示更多Twisted的功能,而不仅仅是当前可用示例中的内容。我希望在完成后能让Twisted的相关人员审查这些示例,他们可能会考虑将其提供给其他人。
祝你在Twisted的旅程中好运!
你不需要改变运行反应器的方式来实现这个功能。
相反,你只需要明白,程序中的一切都是对某个事件的反应。
比如,你什么时候会发出“快乐的长周末”通知呢?当然是在长周末要开始的时候。换句话说,日历(这只是一种特殊的时间记录工具)会生成一个事件,而你则对此做出反应。你可以用IReactorTime.callLater
来实现这个功能:计算到下一个长周末的时间,然后用reactor.callLater(that_delay, some_function)
来设置反应。
如果你想在用户点击按钮时做点什么,那就是对图形用户界面库生成的事件的反应。如果你想在USB设备连接时做点什么,那就是对平台的硬件抽象层(HAL)生成的事件的反应(或者类似DBUS或udev的东西)。
每当你觉得某个东西“自己在行动”时,想想它为什么会这样做——是在什么条件下,或者是对什么情况的反应——这样你就能找到它实际上在反应的事件是什么了。