在reactor.run()后向Twisted SSH发送命令的可靠方法

3 投票
2 回答
1034 浏览
提问于 2025-04-16 00:20

大家好,这是一个关于Python的Twisted SSH库的问题。

我看到的所有示例代码和生产代码,作为基于twisted.conch.ssh的SSH客户端,都是以这样的方式与服务器互动的:

  • 准备一些要远程执行的命令;
  • 定义一些回调函数;
  • 启动反应器,然后暂停等待新的反馈;

在调用reactor.run()之后,我从没见过有人尝试向sshd发送命令,脚本只是静静地等待。我觉得可能可以通过分叉或生成新的进程来发送命令。不过,由于Twisted的一个优势是它的多路复用机制,所以在作为服务器运行时,它不需要分叉来处理传入的请求。我可以说,作为客户端脚本,不分叉而持续发送请求到服务器是一个合理的需求吗?

对此有什么想法吗?

谢谢!

2 个回答

0

你就像是在试图把一个方形的木头塞进一个圆形的洞里。在Twisted这个框架里,一切都是异步的,所以你需要以不同的方式来考虑事件的顺序。你不能简单地说“这里有10个操作要一个接一个地执行”,那样的想法叫做串行思维。

在Twisted中,你先发出第一个命令,然后注册一个回调函数,这个函数会在第一个命令完成时被触发。当这个回调被触发时,你再发出第二个命令,并注册一个回调函数,这个函数会在第二个命令完成时被触发。就这样,一步一步来。

4

joefis的回答基本上是正确的,不过我觉得加一些例子会更有帮助。首先,有几种方法可以让一些代码在反应器启动后立即运行。

第一种方法很简单:

def f():
    print "the reactor is running now"

reactor.callWhenRunning(f)

另一种方法是使用定时事件,虽然其实没必要这样做,直接用callWhenRunning就可以了:

reactor.callLater(0, f)

你也可以使用底层的API,callWhenRunning就是基于这个API实现的:

reactor.addSystemEventTrigger('after', 'startup', f)

你还可以使用服务。这种方法稍微复杂一点,因为它需要用到twistd(1)(或者其他能把服务系统连接到反应器的东西)。不过你可以写一个这样的类:

from twisted.application.service import Service

class ThingDoer(Service):
    def startService(self):
        print "The reactor is running now."

然后写一个.tac文件,内容可以是这样的:

from twisted.application.service import Application

from thatmodule import ThingDoer

application = Application("Do Things")
ThingDoer().setServiceParent(application)

最后,你可以用twistd(1)来运行这个.tac文件:

$ twistd -ny thatfile.tac

当然,这只告诉你如何在反应器运行后做一件事,这并不是你问的全部内容。不过思路是一样的——你定义一个事件处理器,并请求通过调用这个处理器来接收一个事件;当这个处理器被调用时,你就可以执行一些操作。这个思路同样适用于你在Conch中做的任何事情。

你可以在Conch示例中看到这一点,比如在sshsimpleclient.py中,我们有:

class CatChannel(channel.SSHChannel):
    name = 'session'

    def openFailed(self, reason):
        print 'echo failed', reason

    def channelOpen(self, ignoredData):
        self.data = ''
        d = self.conn.sendRequest(self, 'exec', common.NS('cat'), wantReply = 1)
        d.addCallback(self._cbRequest) 

    def _cbRequest(self, ignored):
        self.write('hello conch\n')
        self.conn.sendEOF(self)

    def dataReceived(self, data):
        self.data += data

    def closed(self):
        print 'got data from cat: %s' % repr(self.data)
        self.loseConnection()
        reactor.stop()

在这个例子中,channelOpen是当一个新通道打开时被调用的事件处理器。它会向服务器发送请求。然后它会得到一个Deferred,并为它附加一个回调。这个回调是一个事件处理器,当请求成功时(在这个例子中,就是当cat被执行时)会被调用。_cbRequest就是它附加的回调,这个方法会执行下一步——向通道写入一些字节,然后关闭它。接着是dataReceived事件处理器,它在通道收到字节时被调用,还有closed事件处理器,在通道关闭时被调用。

所以你可以看到这里有四个不同的事件处理器,其中一些是启动操作,最终会触发后面的事件处理器。

回到你关于一个接一个做事情的问题,如果你想依次打开两个cat通道,那么在closed事件处理器中可以打开一个新通道(而不是像这个例子中那样停止反应器)。

撰写回答