68

当我尝试接收大量数据时,它会被切断,我必须按 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()
4

12 回答 12

152

TCP/IP 是基于流的协议,而不是基于消息的协议。不能保证一个对等方的每次send()调用都会导致另一个对等方的单个调用接收到发送的确切数据——它可能会收到数据碎片,由于数据包碎片,recv()它会被拆分为多个调用。recv()

您需要在 TCP 之上定义自己的基于消息的协议,以区分消息边界。然后,要阅读一条消息,您将继续调用recv(),直到您阅读了整条消息或发生错误。

发送消息的一种简单方法是在每条消息前面加上它的长度。然后读取消息,首先读取长度,然后读取那么多字节。您可以这样做:

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 = bytearray()
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data.extend(packet)
    return data

然后您可以使用send_msgandrecv_msg函数来发送和接收整个消息,并且它们不会在网络级别拆分或合并数据包时遇到任何问题。

于 2013-07-16T04:27:25.460 回答
30

您可以将其用作: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
于 2013-07-17T10:43:35.507 回答
16

接受的答案很好,但是对于大文件来说它会很慢 - 字符串是一个不可变的类,这意味着每次使用+符号时都会创建更多对象,list用作堆栈结构会更有效。

这应该会更好

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

print "".join(fragments)
于 2016-12-23T12:26:13.130 回答
10

大多数答案都描述了某种recvall()方法。如果接收数据时的瓶颈是在for循环中创建字节数组,我对三种在方法中分配接收数据的recvall()方法进行了基准测试:

字节串法:

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

结果:

在此处输入图像描述

于 2019-07-23T16:53:26.333 回答
5

您可能需要多次调用 conn.recv() 才能接收所有数据。由于 TCP 流不维护帧边界(即它们仅作为原始字节流工作,而不是结构化消息流),因此不能保证一次调用它会带来所有发送的数据.

有关该问题的另一种描述,请参见此答案

请注意,这意味着您需要通过某种方式知道何时收到了所有数据。如果发件人总是准确地发送 8000 个字节,您可以计算到目前为止您收到的字节数,然后从 8000 中减去该字节数,以了解还剩多少要接收;如果数据是可变大小的,则可以使用各种其他方法,例如让发送者在发送消息之前发送一个字节数的标头,或者如果正在发送的是 ASCII 文本,您可以查找换行符或 NUL 字符。

于 2013-07-16T04:21:29.737 回答
3

免责声明:在极少数情况下您确实需要这样做。如果可能,请使用现有的应用层协议或定义您自己的协议,例如。在每条消息之前加上一个固定长度的整数,指示后面的数据长度,或者用 '\n' 字符终止每条消息。(亚当·罗森菲尔德的回答很好地解释了这一点)

话虽如此,有一种方法可以读取套接字上的所有可用数据。但是,依赖这种通信方式是个坏主意,因为它会带来丢失数据的风险。使用此解决方案时要格外小心,并且只有在阅读下面的说明后才能使用。

def recvall(sock):
    BUFF_SIZE = 4096
    data = bytearray()
    while True:
        packet = sock.recv(BUFF_SIZE)
        if not packet:  # Important!!
            break
        data.extend(packet)
    return data

现在这if not packet:条线是绝对关键的!此处的许多答案建议使用if len(packet) < BUFF_SIZE:已损坏的条件,并且很可能会导致您过早关闭连接并丢失数据。它错误地假设 TCP 套接字一端的一次发送对应于另一端发送的字节数的一次接收。它不是。即使仍有数据等待接收,也很有可能sock.recv(BUFF_SIZE)返回一个比它更小的块。BUFF_SIZE这里这里对这个问题有很好的解释。

通过使用上述解决方案,如果连接的另一端写入数据的速度比您读取的速度慢,您仍然面临数据丢失的风险。您可能只是简单地使用您的所有数据并在有更多数据时退出。有一些方法需要使用并发编程,但这是它自己的另一个主题。

于 2020-04-06T17:58:29.430 回答
2

使用生成器函数的变体(我认为它更 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))
于 2015-12-12T03:48:31.057 回答
2

您可以使用序列化来做到这一点

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

于 2018-11-05T17:42:23.717 回答
0

修改 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

但是,我会大力鼓励使用原始方法。

于 2015-02-04T23:07:33.343 回答
0

对于在您事先不知道数据包长度的情况下正在寻找答案的任何其他人。这是一个简单的解决方案,一次读取 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
于 2019-07-24T09:32:29.067 回答
0

此代码在 32 次迭代中从套接字编程-python 中从服务器接收的缓冲区中读取 1024*32(=32768) 个字节:

jsonString = bytearray()

for _ in range(32):

    packet = clisocket.recv(1024)
    if not packet:
       break
    jsonString.extend(packet)

数据驻留在 jsonString 变量中

于 2021-05-07T14:24:30.100 回答
0

我认为这个问题已经得到了很好的回答,但我只想添加一个使用 Python 3.8 和新赋值表达式(海象运算符)的方法,因为它在风格上很简单。

import socket

host = "127.0.0.1"
port = 31337
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host,port))
s.listen()
con, addr = s.accept()
msg_list = []

while (walrus_msg := con.recv(3)) != b'\r\n':
    msg_list.append(walrus_msg)

print(msg_list)

在这种情况下,从套接字接收 3 个字节并立即分配给walrus_msg. 一旦套接字接收到 ab'\r\n'它就会中断循环。walrus_msg被添加到 amsg_list并在循环中断后打印。这个脚本是基本的,但是已经过测试并且可以与 telnet 会话一起使用。

注意: 需要括号(walrus_msg := con.recv(3))。没有这个,while walrus_msg := con.recv(3) != b'\r\n':评估walrus_msgTrue不是套接字上的实际数据。

于 2020-01-13T16:21:28.807 回答