views:

46

answers:

2

Guys this is a question about python twisted ssh lib.

All sample code even production code I saw acting as a ssh client based on twisted.conch.ssh are all interacting with server in such a mode:

  • prepare some commands to run remotely;
  • define call backs;
  • kick off reactor then suspend for new feedback;

After the reactor.run(), I never found people tried to deliver commands to sshd, the script just sit their waiting. I think it'll be possible to fork or spawn stuffs to send commands. However since one of twisted's strengths is its de-multiplexing mechanism so it doesn't have to fork to process incoming requests when running as a server. May I say it is a reasonable requirement not to fork (as a client script) to continuously send requests to server?

Any thought on this ?

TIA.

A: 

You're trying to put a square peg in a round hole. Everything in Twisted is asynchronous, so you have to think about the sequence of events differently. You can't say "here are 10 operations to be run one after the other" that's serial thinking.

In Twisted you issue the first command and register a callback that will be triggered when it completes. When that callback occurs you issue the 2nd command and register a callback that will be triggered when that completes. And so on and so on.

joefis
+1  A: 

joefis' answer is basically sound, but I bet some examples would be helpful. First, there are a few ways you can have some code run right after the reactor starts.

This one is pretty straightforward:

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

reactor.callWhenRunning(f)

Another way is to use timed events, although there's probably no reason to do it this way instead of using callWhenRunning:

reactor.callLater(0, f)

You can also use the underlying API which callWhenRunning is implemented in terms of:

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

You can also use services. This is a bit more involved, since it involves using using twistd(1) (or something else that's going to hook the service system up to the reactor). But you can write a class like this:

from twisted.application.service import Service

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

And then write a .tac file like this:

from twisted.application.service import Application

from thatmodule import ThingDoer

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

And finally, you can run this .tac file using twistd(1):

$ twistd -ny thatfile.tac

Of course, this only tells you how to do one thing after the reactor is running, which isn't exactly what you're asking. It's the same idea, though - you define some event handler and ask to receive an event by having that handler called; when it is called, you get to do stuff. The same idea applies to anything you do with Conch.

You can see this in the Conch examples, for example in sshsimpleclient.py we have:

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()

In this example, channelOpen is the event handler called when a new channel is opened. It sends a request to the server. It gets back a Deferred, to which it attaches a callback. That callback is an event handler which will be called when the request succeeds (in this case, when cat has been executed). _cbRequest is the callback it attaches, and that method takes the next step - writing some bytes to the channel and then closing it. Then there's the dataReceived event handler, which is called when bytes are received over the chnanel, and the closed event handler, called when the channel is closed.

So you can see four different event handlers here, some of which are starting operations that will eventually trigger a later event handler.

So to get back to your question about doing one thing after another, if you wanted to open two cat channels, one after the other, then in the closed event handler could open a new channel (instead of stopping the reactor as it does in this example).

Jean-Paul Calderone