4

私は次のようにクライアントサーバーアプリケーションを作成します:client(c#)<-> server(twisted; ftp proxy and 追加機能)<-> ftp server

サーバーには2つのクラスがあります。LineReceieverプロトコルから継承された独自のクラスプロトコルと、twisted.protocols.ftpからのFTPClientです。

ただし、クライアントが大きなファイル(10 Gb〜20 Gb)を送信または取得すると、サーバーはMemoryErrorをキャッチします。コードでバッファを使用していません。これは、呼び出し後にtransport.write(data)データがreactorのライターの内部バッファーに追加されたときに発生します(間違っている場合は修正してください)。

この問題を回避するには何を使用すればよいですか?または、問題へのアプローチを変更する必要がありますか?

大きなストリームの場合は、IConsumerおよびIProducerインターフェイスを使用する必要があることがわかりました。しかし最後に、transfer.writeメソッドを呼び出し、効果は同じになります。それとも私は間違っていますか?

UPD:

ファイルのダウンロード/アップロードのロジックは次のとおりです(ftpからTwistedサーバーを経由してWindowsのクライアントに):

クライアントはいくつかのヘッダーをツイストサーバーに送信し、その後ファイルの送信を開始します。ツイストサーバーはヘッダーを受信し、その後(必要に応じて)呼び出し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から twisted.protocols.ftp)を開き、FTPManagerフィールド_ftpClientAsyncのオブジェクトを設定_ftpCWDSuccees(self, protocol, fileName)protocol = obSSDMProtocol、ファイルのバイトを受信したときdataReceived(self, data)にFileReceiverオブジェクトを呼び出すメソッドを呼び出します。

また、self.__proto.transport.write(data)呼び出されると、データはクライアントに送り返すよりも速く内部バッファに追加されるため、メモリが不足します。バッファが特定のサイズに達したときに読み取りを停止し、バッファがすべてクライアントに送信された後に読み取りを再開できますか?またはそのようなもの?

4

1 に答える 1

14

20 ギガバイト (ギガビット?) の文字列をtransport.writeに渡す場合、少なくとも 20 ギガバイト (ギガバイト?) のメモリが必要になります。Python で文字列を処理するときに余分なコピーが必要になるため、おそらく 40 または 60 程度のメモリが必要になります。 .

transport.write20 ギガバイト (ギガビット?) の文字列を 1 つも渡さなかったとしてもtransport.write、短い文字列を使用してネットワークが処理できない速度で繰り返し呼び出すと、最終的に送信バッファーが大きくなりすぎてメモリに収まらなくなります。に遭遇しMemoryErrorます。

これらの問題の両方を解決するのが生産者/消費者システムです。IProducerandを使用する利点IConsumerは、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 レベルで実行する方法を次に示します。

于 2012-10-15T13:55:49.213 に答える