扭曲的大文件传输

时间:2012-10-15 07:40:38

标签: twisted file-transfer

我写这样的客户端 - 服务器应用程序: 客户(c#)< - >服务器(扭曲; ftp代理和附加功能)< - > ftp服务器

服务器有两个类:我自己的类协议继承自LineReceiever协议,FTPClient来自twisted.protocols.ftp。

但是当客户端发送或获取大文件(10 Gb - 20 Gb)时,服务器会捕获MemoryError。我的代码中没有使用任何缓冲区。它发生在调用transport.write(data)数据附加到reactor的编写器的内部缓冲区后(如果我错了,请纠正我)。

我应该使用什么来避免这个问题?或者我应该改变问题的方法吗?

我发现对于大流,我应该使用IConsumer和IProducer接口。但最后它会调用transfer.write方法和效果会一样。或者我错了吗?

UPD:

这是文件下载/上传的逻辑(从ftp到Twisted服务器到Windows上的客户端):

客户端向Twisted服务器发送一些标头,然后开始发送文件。 Twisted服务器接收标头,然后(如果需要)调用setRawMode(),打开ftp连接并从/向客户端接收/发送字节,并在所有紧密连接之后。以下是上传文件的代码的一部分:

FTPManager类

def _ftpCWDSuccees(self, protocol, fileName):
        self._ftpClientAsync.retrieveFile(fileName, FileReceiver(protocol))



class FileReceiver(Protocol):
    def __init__(self, proto):
        self.__proto = proto

    def dataReceived(self, data):
        self.__proto.transport.write(data)

    def connectionLost(self, why = connectionDone):
        self.__proto.connectionLost(why)

主代理服务器类:

class SSDMProtocol(LineReceiver)
...

在SSDMProtocol对象(调用obSSDMProtocol)解析头之后,它调用打开ftp连接(FTPClient的{​​{1}})并设置FTPManager字段_ftpClientAsync的对象并调用twisted.protocols.ftp的方法使用_ftpCWDSuccees(self, protocol, fileName)并且当收到文件的字节时,调用FileReceiver对象的protocol = obSSDMProtocol

当调用dataReceived(self, data)时,数据追加到内部缓冲区比发送回客户端更快,因此内存耗尽。可能是我可以在缓冲区达到一定大小时停止读取并在缓冲区全部发送到客户端后继续读取?或类似的东西?

1 个答案:

答案 0 :(得分:14)

如果您将一个20千兆字节(千兆位?)的字符串传递给transport.write,那么您将需要至少20千兆字节(千兆位?)的内存 - 由于额外的内存可能更像是40或60在Python中处理字符串时必须进行复制。

即使您从未将单个字符串传递给{千兆位(千兆字节?)的transport.write,如果您以短于网络可以处理的速率重复拨打短字符串transport.write,则发送缓冲区最终会变得太大而不适合内存,你会遇到MemoryError

这两个问题的解决方案是生产者/消费者系统。使用IProducerIConsumer的优势在于,您永远不会拥有20千兆字节(千兆位?)的字符串,并且您永远不会填充包含太多短字符串的发送缓冲区。网络将受到限制,因此字节的读取速度不会超过应用程序处理它们并忘记它们的速度。你的字符串最终将达到16kB - 64kB的数量级,这应该很容易适合内存。

您只需调整FileReceiver的使用,以包含传入连接的注册作为传出连接的生产者:

class FileReceiver(Protocol):
    def __init__(self, outgoing):
        self._outgoing = outgoing

    def connectionMade(self):
        self._outgoing.transport.registerProducer(self.transport, streaming=True)

    def dataReceived(self, data):
        self._outgoing.transport.write(data)

现在只要self._outgoing.transport的发送缓冲区填满,它就会告诉self.transport暂停。一旦发送缓冲区清空,它将告诉self.transport恢复。 self.transport了解如何在TCP级别执行这些操作,以便进入服务器的数据也会变慢。