2011-03-04 51 views
10

我想实现一个非常简单的文件传输客户端在Python中使用扭曲的海螺。客户端应该以编程方式将几个文件传输到远程的ssh/sftp服务器。该功能被给予用户名,密码,文件列表,目标服务器:目录,只需要以跨平台的方式进行验证和复制。扭曲海螺文件传输

我已经阅读了关于twisted的一些介绍性资料,并设法使自己的SSH客户端在远程服务器上执行cat。我真的很难把这个扩展到移动文件。我看了一下cftp.py和文件传输测试,但是完全被扭曲了。

有没有人有任何建议或参考,可以指出我在正确的方向吗? 我已经构建的SSH客户端基于this one

+0

你能解释一下你是如何陷入更具体的吗?现在你的问题是,我能想到的唯一方法就是编写一个完整的海螺/ SFTP教程,这对于SO来说可能比15分更有用(至少目前是这样)。 ;)但更具体的问题可能有一个更简单的答案。 – 2011-03-04 16:17:10

+0

@让 - 保罗现在我_think_我需要继承t.c.s.f.FileTransferClient。我还想_我需要打开一个类似于上面链接的示例的SSH连接。我坚持如何正确地继承t.c.s.f.FileTransferClient的子类以及如何实际移动文件。完整的教程并不是必要的,因为我有兴趣学习扭曲(这是我的第一个小项目),但是我应该使用或阅读哪些方法和类的草图,甚至是文档中的简单例子(我发现cftp.py难以阅读)将不胜感激。 – rymurr 2011-03-04 16:50:34

回答

32

用扭曲海螺进行SFTP文件传输涉及到几个不同的阶段(当然,如果你斜视它们,它们是不同的)。基本上,首先你需要建立一个连接,并在其上打开一个通道,并在其上运行一个sftp子系统。呼。然后,您可以使用连接到该通道的FileTransferClient实例的方法来执行要执行的任何SFTP操作。

通过twisted.conch.client包中的模块提供的API可以为您设置获取SSH连接的最基本要求。下面是在一个稍微不那么令人惊讶的接口包扎的twisted.conch.client.default.connect轻微古怪的函数:

from twisted.internet.defer import Deferred 
from twisted.conch.scripts.cftp import ClientOptions 
from twisted.conch.client.connect import connect 
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey 

def sftp(user, host, port): 
    options = ClientOptions() 
    options['host'] = host 
    options['port'] = port 
    conn = SFTPConnection() 
    conn._sftp = Deferred() 
    auth = SSHUserAuthClient(user, options, conn) 
    connect(host, port, options, verifyHostKey, auth) 
    return conn._sftp 

此功能需要一个用户名,主机名(或IP地址)和端口号,并设置了一个经过验证的SSH连接服务器在该地址使用与给定用户名关联的帐户。

实际上,它做的比这更多一点,因为SFTP设置在这里有点混杂。尽管如此,忽略SFTPConnection_sftp推迟。

ClientOptions基本上只是一个奇特的字典,connect希望能够看到它连接到什么,所以它可以验证主机密钥。

SSHUserAuthClient是定义如何进行身份验证的对象。这个班级知道如何尝试通常的事情,比如查看~/.ssh并与本地SSH代理交谈。如果你想改变身份验证的方式,这是一个可以玩的对象。你也可以继承SSHUserAuthClient并覆盖其getPasswordgetPublicKeygetPrivateKey,和/或signData方法,或者你可以写有你想要的任何其他身份验证逻辑自己完全不同的类。看一下实现,看看SSH协议实现调用什么方法来完成身份验证。

所以这个功能将建立一个SSH连接和验证它。完成之后,SFTPConnection实例即可发挥作用。请注意0​​如何将SFTPConnection实例作为参数。一旦验证成功,它将切断对该实例的连接控制。特别是,该实例已调用serviceStarted。下面是完整的实施SFTPConnection类:

class SFTPConnection(SSHConnection): 
    def serviceStarted(self): 
     self.openChannel(SFTPSession()) 

很简单:它是所有开放的新渠道。它通过的SFTPSession实例可以与该新通道进行交互。我是这样定义的SFTPSession

class SFTPSession(SSHChannel): 
    name = 'session' 

    def channelOpen(self, whatever): 
     d = self.conn.sendRequest(
      self, 'subsystem', NS('sftp'), wantReply=True) 
     d.addCallbacks(self._cbSFTP) 


    def _cbSFTP(self, result): 
     client = FileTransferClient() 
     client.makeConnection(self) 
     self.dataReceived = client.dataReceived 
     self.conn._sftp.callback(client) 

像与SFTPConnection,这个类有一个当连接已经准备好了被调用方法。在这种情况下,当通道成功打开时调用它,方法是channelOpen

最后,启动SFTP子系统的要求已到位。因此,channelOpen通过频道发送请求以启动该子系统。它要求回复,以便它可以知道何时成功(或失败)。它为Deferred添加了一个回调函数,它将FileTransferClient连接到自身。

FileTransferClient实例将实际上格式化和解析通过此连接通道移动的字节。换句话说,它是一个执行只是的SFTP协议。它运行在SSH协议上,这个例子创建了其他对象。但就其而言,它在其dataReceived方法中接收字节,解析它们并将数据分派给回调函数,并提供接受结构化Python对象,将这些对象格式化为正确字节并将它们写入其传输的方法。

尽管如此,这对于使用它并不重要。但是,在举例说明如何执行SFTP操作之前,我们先介绍一下_sftp属性。这是我粗略的方法,使这个新连接的FileTransferClient实例可用于其他代码,它实际上会知道如何处理它。将SFTP设置代码从实际使用SFTP连接的代码中分离出来,更容易在重新使用前者的同时更改后者。

所以Deferred我设置在sftp被解雇了FileTransferClient连接_cbSFTP。和sftp调用者得到了Deferred归还给他们,因此代码可以做这样的事情:

def transfer(client): 
    d = client.makeDirectory('foobarbaz', {}) 
    def cbDir(ignored): 
     print 'Made directory' 
    d.addCallback(cbDir) 
    return d 


def main(): 
    ... 
    d = sftp(user, host, port) 
    d.addCallback(transfer) 

所以第一sftp建立全连接,一路向上连接本地FileTransferClient实例字节流在另一端具有一些SSH服务器的SFTP子系统,然后transfer接受该实例并使用它创建一个目录,使用FileTransferClient中的一种方法执行某些SFTP操作。

下面是一个完整的代码清单,你应该能够运行,并看到一些SFTP服务器上创建一个目录:

from sys import stdout 

from twisted.python.log import startLogging, err 

from twisted.internet import reactor 
from twisted.internet.defer import Deferred 

from twisted.conch.ssh.common import NS 
from twisted.conch.scripts.cftp import ClientOptions 
from twisted.conch.ssh.filetransfer import FileTransferClient 
from twisted.conch.client.connect import connect 
from twisted.conch.client.default import SSHUserAuthClient, verifyHostKey 
from twisted.conch.ssh.connection import SSHConnection 
from twisted.conch.ssh.channel import SSHChannel 


class SFTPSession(SSHChannel): 
    name = 'session' 

    def channelOpen(self, whatever): 
     d = self.conn.sendRequest(
      self, 'subsystem', NS('sftp'), wantReply=True) 
     d.addCallbacks(self._cbSFTP) 


    def _cbSFTP(self, result): 
     client = FileTransferClient() 
     client.makeConnection(self) 
     self.dataReceived = client.dataReceived 
     self.conn._sftp.callback(client) 



class SFTPConnection(SSHConnection): 
    def serviceStarted(self): 
     self.openChannel(SFTPSession()) 


def sftp(user, host, port): 
    options = ClientOptions() 
    options['host'] = host 
    options['port'] = port 
    conn = SFTPConnection() 
    conn._sftp = Deferred() 
    auth = SSHUserAuthClient(user, options, conn) 
    connect(host, port, options, verifyHostKey, auth) 
    return conn._sftp 


def transfer(client): 
    d = client.makeDirectory('foobarbaz', {}) 
    def cbDir(ignored): 
     print 'Made directory' 
    d.addCallback(cbDir) 
    return d 


def main(): 
    startLogging(stdout) 

    user = 'exarkun' 
    host = 'localhost' 
    port = 22 
    d = sftp(user, host, port) 
    d.addCallback(transfer) 
    d.addErrback(err, "Problem with SFTP transfer") 
    d.addCallback(lambda ignored: reactor.stop()) 
    reactor.run() 


if __name__ == '__main__': 
    main() 

makeDirectory是一个相当简单的操作。 makeDirectory方法返回一个Deferred,该目录在创建目录时触发(或者如果发生错误)。传输文件有一点涉及,因为您必须提供要发送的数据,或者定义在下载而不是上传时如何解释接收的数据。

如果您阅读FileTransferClient方法的文档字符串,您应该看看如何使用其他功能 - 对于实际的文件传输,openFile主要是感兴趣的。它会为您提供一个Deferred,它会与ISFTPFile供应商一起启动。该对象具有读写文件内容的方法。

+0

非常感谢本教程。它帮助了很多,现在事情更加清晰。我现在拥有一切工作!我期待着在未来扭转更多的东西 – rymurr 2011-03-06 15:41:08

+0

很好的解释,但你能详细说明'self.dataReceived = client.dataReceived'吗? – daf 2012-04-04 21:44:36

0

SSH客户端不是独立于其他OS服务的东西。你真的想增加对.ssh文件夹,钥匙串等的支持吗?可能更快更健壮的方式是在Windows下使用scp(Linux,OSX)和pscp。这种方式看起来更像“Linux方式”(将现有的小块链接成复杂的东西)。

+1

我对twisted和conch的理解是,你可以实现独立于操作系统的SSH服务。 '.ssh'文件夹等对于我所要做的并不重要。远程GUI只是在安全的网络中向脚本和一些参数发送一个集群,所以它不需要过于安全。尽管如此,群集的唯一途径是通过SSH。 – rymurr 2011-03-04 15:25:42