扭曲的海螺文件传输

Posted

技术标签:

【中文标题】扭曲的海螺文件传输【英文标题】:twisted conch filetransfer 【发布时间】:2011-07-08 21:38:06 【问题描述】:

我正在尝试使用扭曲的海螺在 python 中实现一个非常简单的文件传输客户端。客户端应该以编程方式简单地将一些文件传输到远程 ssh/sftp 服务器。该函数给出了用户名、密码、文件列表、目的服务器:目录,只需要跨平台进行认证和复制即可。

我已经阅读了一些关于 twisted 的介绍性材料,并设法制作了我自己的 SSH 客户端,它只在远程服务器上执行 cat。我很难将其扩展到移动文件。我查看了 cftp.py 和文件传输测试,但完全被扭曲了。

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

【问题讨论】:

你能更具体地解释一下你是如何陷入困境的吗?正如你现在的问题,我能想到的唯一回答方法是编写一个完整的 Conch/SFTP 教程,这可能比 SO 上的 15 分更值得(至少目前如此)。 ;) 但更具体的问题可能有更简单的答案。 @Jean-Paul 现在我认为我需要继承 t.c.s.f.FileTransferClient。我还 认为 我需要打开一个类似于我上面链接的示例的 SSH 连接。我被如何正确地继承 t.c.s.f.FileTransferClient 以及如何实际移动文件所困扰。不需要完整的教程,因为我有兴趣学习扭曲(这是我的第一个小项目),但我应该使用或阅读哪些方法和类的草图,甚至是文档中的简单示例(我发现cftp.py 难以阅读)将不胜感激。 【参考方案1】:

使用 Twisted Conch 进行 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 Deferred。

ClientOptions 基本上只是一个精美的字典,connect 希望能够看到它正在连接什么,以便验证主机密钥。

SSHUserAuthClient 是定义身份验证方式的对象。此类知道如何尝试常见的事情,例如查看~/.ssh 并与本地 SSH 代理交谈。如果您想更改身份验证的完成方式,这是可以玩的对象。您可以继承SSHUserAuthClient 并覆盖其getPasswordgetPublicKeygetPrivateKey 和/或signData 方法,或者您可以编写自己的完全不同的类,该类具有您想要的任何其他身份验证逻辑。查看实现,了解 SSH 协议实现调用了哪些方法来完成身份验证。

所以这个函数将建立一个 SSH 连接并对其进行身份验证。完成后,SFTPConnection 实例开始发挥作用。请注意 SSHUserAuthClient 如何将 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 连接的代码分开,可以更轻松地重用前者,同时更改后者。

所以我在sftp 中设置的Deferred 被连接在_cbSFTP 中的FileTransferClient 触发。而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 提供程序一起触发。该对象具有读取和写入文件内容的方法。

【讨论】:

非常感谢您的教程。它有很大帮助,现在事情变得更加清晰了。我现在一切正常!我期待在未来用twisted 做更多的事情 很好的解释,但你能详细说明self.dataReceived = client.dataReceived吗?【参考方案2】:

SSH 客户端并不独立于其他操作系统服务。您真的想为.ssh 文件夹、钥匙串等添加支持吗?可能更快速和健壮的方法是在 Windows 下围绕 scp (Linux, OSX) 和 pscp 进行包装。而且这种方式看起来更像是“Linux 方式”(将现有的小块链接成复杂的东西)。

【讨论】:

我对twisted和conch的理解是可以实现独立于操作系统的SSH服务。 .ssh 文件夹等对于我想要做的事情并不重要。远程 gui 只是将脚本和一些参数发送到安全网络中的集群,因此它不需要过于安全。不过,通往集群的唯一方法是通过 SSH。

以上是关于扭曲的海螺文件传输的主要内容,如果未能解决你的问题,请参考以下文章

Netty如何传输文件

怎么用WebService传输XML文件

Nginx+PHP Xsendfile文件传输

文件传输行云管家文件传输有什么好处?

关于用JAVA的SOCKET传输文件

怎么用JAVA实现大文件分段传输