web-dev-qa-db-ja.com

Pythonソケットは大量のデータを受信します

大量のデータを受信しようとすると、データが切断され、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()
41
user2585107

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はメッセージ全体を送受信するために機能します。ネットワークレベルでパケットが分割または合体しても問題はありません。

108
Adam Rosenfield

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
19
JadedTuna

受け入れられる答えは結構ですが、大きなファイルでは本当に遅くなります-stringは不変クラスです。つまり、listをスタック構造として使用して、+記号を使用するたびに、より多くのオブジェクトが作成されます。より効率的になります。

これはうまくいくはずです

while True: 
    chunk = s.recv(10000)
    if not chunk: 
        break
    fragments.append(chunk)

print "".join(fragments)
8
Mina Gabriel

すべてのデータを受信するには、conn.recv()を複数回呼び出す必要がある場合があります。一度だけ呼び出すと、TCPストリームはフレーム境界を維持しない(つまり、生のストリームとしてのみ機能する)ため、送信されたすべてのデータを取り込むことが保証されませんメッセージの構造化されたストリームではなく、バイト)。

問題の別の説明については、 this answer を参照してください。

これは、すべてのデータをいつ受信したかを知る何らかの方法が必要であることを意味します。送信者が常に正確に8000バイトを送信する場合、これまでに受信したバイト数をカウントし、8000からそれを減算して、受信するために残っているバイト数を知ることができます。データのサイズが可変の場合、メッセージを送信する前に送信者にバイト数ヘッダーを送信させる、またはASCII text送信されている場合、改行またはNUL文字を探すことができます。

4
Jeremy Friesner

ジェネレーター関数を使用したバリエーション(私はもっと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))
3
yoniLavi

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

ただし、元のアプローチを使用することを強くお勧めします。

1
sjMoquin

ほとんどの答えは、ある種の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

結果:

enter image description here

0
Jacob Stern

以前にパケットの長さがわからない場合に答えを探している他の人のために。これは、一度に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
0
vatsug

シリアル化を使用してそれを行うことができます

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にエンコード/デコードする必要があります

0
John Albert