これは、前のコメントへのコメントに基づいた新しい回答です。
単一の TCP ソケットを使用し、すべて 1 つの大きなストリームで、各ファイルの名前と内容をnetstringsとして交互に送信することで、各ファイルを送信します。
私は Python 2.6 を想定しており、両側のファイルシステムが同じエンコーディングを使用しており、多数の同時クライアントを必要としない (ただし、実際のクライアントとテスターなど、場合によっては 2 つ必要になる場合もあります)。 . また、メソッドが に登録し、通知をキューに入れ、それらを 1 つずつ s するモジュールがfilegenerator
あると仮定しています。generate()
inotify
yield
client.py:
import contextlib
import socket
import filegenerator
sock = socket.socket()
with contextlib.closing(sock):
sock.connect((HOST, 12345))
for filename in filegenerator.generate():
with open(filename, 'rb') as f:
contents = f.read()
buf = '{0}:{1},{2}:{3},'.format(len(filename), filename,
len(contents), contents)
sock.sendall(buf)
サーバー.py:
import contextlib
import socket
import threading
def pairs(iterable):
return zip(*[iter(iterable)]*2)
def netstrings(conn):
buf = ''
while True:
newbuf = conn.recv(1536*1024)
if not newbuf:
return
buf += newbuf
while True:
colon = buf.find(':')
if colon == -1:
break
length = int(buf[:colon])
if len(buf) >= colon + length + 2:
if buf[colon+length+1] != ',':
raise ValueError('Not a netstring')
yield buf[colon+1:colon+length+1]
buf = buf[colon+length+2:]
def client(conn):
with contextlib.closing(conn):
for filename, contents in pairs(netstrings(conn)):
with open(filename, 'wb') as f:
f.write(contents)
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
with contextlib.closing(sock):
sock.bind(('0.0.0.0', 12345))
sock.listen(1)
while True:
conn, addr = sock.accept()
t = threading.Thread(target=client, args=[conn])
t.daemon = True
t.start()
Windows で約 200 を超えるクライアント、Linux および BSD (Mac を含む) で 100 を超えるクライアント、あまり良くないプラットフォームで 10 を超えるクライアントが必要な場合は、Linux で BSD を使用して、スレッド化された設計ではなくイベント ループ設計を使用することをお勧めしますepoll
。kqueue
、および Windows の IO 完了ポート。これは大変なことですが、幸いなことに、すべてをまとめてくれるフレームワークがあります。2 つの一般的な (そして非常に異なる) 選択肢はTwistedとgeventです。
特に優れている点の1 つgevent
は、現在スレッド化されたコードを記述できることです。いくつかの簡単な変更を加えるだけで、魔法のようにイベント ベースのコードに変換できます。
一方、最終的にイベントベースのコードが必要になる場合は、最初からフレームワークを学習して使用accept
する方がよいでしょう。recv
完全なメッセージが表示され、正常にシャットダウンするなど、気になる部分を書き出すだけです。結局のところ、上記のコードの半分以上は基本的に、すべてのサーバーが共有するもののボイラープレートです。
コメントで、あなたは次のように述べました。
また、ファイルはバイナリであるため、クライアントのエンコーディングがサーバーのエンコーディングと異なる場合、問題が発生する可能性があります。
各ファイルをバイナリ モード ('rb'
および'wb'
) で開き、バイナリ文字列を文字として解釈したり、埋め込まれた NUL 文字を EOF などとして処理したりせずに処理できるプロトコル (ネット文字列) を意図的に選択したことに注意してください。そして、私が使用している間、Python 2.x では、文字列をフィードするか、ロケールベースのフォーマット タイプを指定しstr.format
ない限り、暗黙のエンコーディングは行われません。どちらも行っていません。(3.x では、代わりにunicode
を使用する必要があることに注意してください。これにより、コードが少し変更されます。)bytes
str
つまり、クライアントとサーバーのエンコーディングは含まれません。FTP の I モードとまったく同じバイナリ転送を行っています。
しかし、反対に、テキストを転送してターゲット システム用に自動的に再エンコードしたい場合はどうでしょうか。これを行うには、次の 3 つの簡単な方法があります。
- クライアントのエンコーディングを (先頭に 1 回、またはファイルごとに 1 回) 送信し、サーバーでクライアントからデコードして、ローカル ファイルに再エンコードします。
- ソケットを含め、すべてをテキスト/ユニコード モードで実行します。これはばかげており、2.x でも同様に実行するのは困難です。
- ワイヤ エンコーディングを定義します (UTF-8 など)。クライアントは、ファイルのデコードと送信用の UTF-8 へのエンコードを担当します。サーバーは、受信時に UTF-8 をデコードし、ファイルをエンコードします。
3 番目のオプションを使用すると、ファイルがデフォルトのファイルシステム エンコーディングになると仮定すると、変更されたクライアント コードは次のようになります。
with io.open(filename, 'r', encoding=sys.getfilesystemencoding()) as f:
contents = f.read().encode('utf-8')
そしてサーバー上で:
with io.open(filename, 'w', encoding=sys.getfilesystemencoding()) as f:
f.write(contents.decode('utf-8'))
またio.open
、関数はデフォルトでユニバーサル改行を使用するため、クライアントは何でも Unix スタイルの改行に変換し、サーバーは独自のネイティブの改行タイプに変換します。
FTP の T モードは、実際には再エンコードを行わないことに注意してください。改行変換 (およびそのより限定されたバージョン) のみを行います。