twisted - 交互式队列函数

2 投票
3 回答
1188 浏览
提问于 2025-04-18 06:51

我刚刚通过这个很不错的教程接触到了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 个回答

1

你说过:

假设我突然想给所有客户发送一条友好的消息。

还有:

我想要在服务器上运行时,能够在 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 应用的方法,当然这只是众多方法中的一种,其他回答中也提到了很多。

1

正如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的旅程中好运!

2

你不需要改变运行反应器的方式来实现这个功能。

相反,你只需要明白,程序中的一切都是对某个事件的反应。

比如,你什么时候会发出“快乐的长周末”通知呢?当然是在长周末要开始的时候。换句话说,日历(这只是一种特殊的时间记录工具)会生成一个事件,而你则对此做出反应。你可以用IReactorTime.callLater来实现这个功能:计算到下一个长周末的时间,然后用reactor.callLater(that_delay, some_function)来设置反应。

如果你想在用户点击按钮时做点什么,那就是对图形用户界面库生成的事件的反应。如果你想在USB设备连接时做点什么,那就是对平台的硬件抽象层(HAL)生成的事件的反应(或者类似DBUS或udev的东西)。

每当你觉得某个东西“自己在行动”时,想想它为什么会这样做——是在什么条件下,或者是对什么情况的反应——这样你就能找到它实际上在反应的事件是什么了。

撰写回答