2011-03-04 52 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