大量のデータを受信しようとすると、データが切断され、Enterキーを押して残りのデータを取得する必要があります。最初は少し増やすことができましたが、まだすべてを受け取ることはできません。ご覧のとおり、conn.recv()のバッファーを増やしましたが、まだすべてのデータを取得できません。ある時点で切り取ります。残りのデータを受信するには、raw_inputでEnterキーを押す必要があります。とにかく一度にすべてのデータを取得できますか?これがコードです。
port = 7777
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', port))
sock.listen(1)
print ("Listening on port: "+str(port))
while 1:
conn, sock_addr = sock.accept()
print "accepted connection from", sock_addr
while 1:
command = raw_input('Shell> ')
conn.send(command)
data = conn.recv(8000)
if not data: break
print data,
conn.close()
TCP/IPはストリームベースプロトコルであり、メッセージベースプロトコルではありません。 1つのピアによるすべてのsend()
呼び出しが、送信された正確なデータを受信する他のピアによる単一のrecv()
呼び出しになるという保証はありません。 recv()
呼び出し、パケットの断片化による。
メッセージの境界を区別するために、TCPの上に独自のメッセージベースのプロトコルを定義する必要があります。その後、メッセージを読み取るために、recv()
を呼び出し続けます'メッセージ全体を読んだか、エラーが発生しました。
メッセージを送信する簡単な方法の1つは、各メッセージの先頭にその長さを付けることです。次に、メッセージを読み取るには、最初に長さを読み取り、次にそのバイト数を読み取ります。その方法は次のとおりです。
def send_msg(sock, msg):
# Prefix each message with a 4-byte length (network byte order)
msg = struct.pack('>I', len(msg)) + msg
sock.sendall(msg)
def recv_msg(sock):
# Read message length and unpack it into an integer
raw_msglen = recvall(sock, 4)
if not raw_msglen:
return None
msglen = struct.unpack('>I', raw_msglen)[0]
# Read the message data
return recvall(sock, msglen)
def recvall(sock, n):
# Helper function to recv n bytes or return None if EOF is hit
data = b''
while len(data) < n:
packet = sock.recv(n - len(data))
if not packet:
return None
data += packet
return data
次に、send_msg
およびrecv_msg
はメッセージ全体を送受信するために機能します。ネットワークレベルでパケットが分割または合体しても問題はありません。
data = recvall(sock)
として使用できます
def recvall(sock):
BUFF_SIZE = 4096 # 4 KiB
data = b''
while True:
part = sock.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
# either 0 or end of data
break
return data
受け入れられる答えは結構ですが、大きなファイルでは本当に遅くなります-stringは不変クラスです。つまり、list
をスタック構造として使用して、+
記号を使用するたびに、より多くのオブジェクトが作成されます。より効率的になります。
これはうまくいくはずです
while True:
chunk = s.recv(10000)
if not chunk:
break
fragments.append(chunk)
print "".join(fragments)
すべてのデータを受信するには、conn.recv()を複数回呼び出す必要がある場合があります。一度だけ呼び出すと、TCPストリームはフレーム境界を維持しない(つまり、生のストリームとしてのみ機能する)ため、送信されたすべてのデータを取り込むことが保証されませんメッセージの構造化されたストリームではなく、バイト)。
問題の別の説明については、 this answer を参照してください。
これは、すべてのデータをいつ受信したかを知る何らかの方法が必要であることを意味します。送信者が常に正確に8000バイトを送信する場合、これまでに受信したバイト数をカウントし、8000からそれを減算して、受信するために残っているバイト数を知ることができます。データのサイズが可変の場合、メッセージを送信する前に送信者にバイト数ヘッダーを送信させる、またはASCII text送信されている場合、改行またはNUL文字を探すことができます。
ジェネレーター関数を使用したバリエーション(私はもっとPythonicだと思います):
def recvall(sock, buffer_size=4096):
buf = sock.recv(buffer_size)
while buf:
yield buf
if len(buf) < buffer_size: break
buf = sock.recv(buffer_size)
# ...
with socket.create_connection((Host, port)) as sock:
sock.sendall(command)
response = b''.join(recvall(sock))
Adam Rosenfieldのコードの変更:
import sys
def send_msg(sock, msg):
size_of_package = sys.getsizeof(msg)
package = str(size_of_package)+":"+ msg #Create our package size,":",message
sock.sendall(package)
def recv_msg(sock):
try:
header = sock.recv(2)#Magic, small number to begin with.
while ":" not in header:
header += sock.recv(2) #Keep looping, picking up two bytes each time
size_of_package, separator, message_fragment = header.partition(":")
message = sock.recv(int(size_of_package))
full_message = message_fragment + message
return full_message
except OverflowError:
return "OverflowError."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
ただし、元のアプローチを使用することを強くお勧めします。
ほとんどの答えは、ある種のrecvall()
メソッドを説明しています。データ受信時のボトルネックがfor
ループでバイト配列を作成している場合、recvall()
メソッドで受信データを割り当てる3つのアプローチのベンチマークを行いました。
バイト文字列メソッド:
arr = b''
while len(arr) < msg_len:
arr += sock.recv(max_msg_size)
リスト方式:
fragments = []
while True:
chunk = sock.recv(max_msg_size)
if not chunk:
break
fragments.append(chunk)
arr = b''.join(fragments)
事前に割り当てられたbytearray
メソッド:
arr = bytearray(msg_len)
pos = 0
while pos < msg_len:
arr[pos:pos+max_msg_size] = sock.recv(max_msg_size)
pos += max_msg_size
結果:
以前にパケットの長さがわからない場合に答えを探している他の人のために。これは、一度に4096バイトを読み取り、4096バイト未満が受信されたときに停止する簡単なソリューションです。ただし、受信したパケットの合計長がちょうど4096バイトの場合は機能しません。その後、recv()
を再度呼び出してハングします。
def recvall(sock):
data = b''
bufsize = 4096
while True:
packet = sock.recv(bufsize)
data += packet
if len(packet) < bufsize:
break
return data
シリアル化を使用してそれを行うことができます
from socket import *
from json import dumps, loads
def recvall(conn):
data = ""
while True:
try:
data = conn.recv(1024)
return json.loads(data)
except ValueError:
continue
def sendall(conn):
conn.sendall(json.dumps(data))
注:上記のコードを使用してファイルを共有する場合は、base64にエンコード/デコードする必要があります