Python Socket 接收大量数据

当我尝试接收大量的数据时,它被切断,我必须按回车键来获得剩余的数据。起初我能够增加它一点点,但它仍然不会收到它的全部。正如您所看到的,我已经增加了 conn.recv ()上的缓冲区,但是它仍然不能获取所有的数据。它会在某个时间点切断它。我必须在原始输入上按 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()
174927 次浏览

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

有关该问题的其他说明,请参见 这个答案

请注意,这意味着您需要某种方法来知道何时收到所有数据。如果发送方总是发送正好8000字节,你可以计算到目前为止你已经收到的字节数,然后从8000中减去剩下的字节数来知道还有多少要接收; 如果数据是可变大小的,还有其他各种各样的方法可以使用,比如让发送方在发送消息之前发送一个字节数的头,或者如果发送的是 ASCII 文本,你可以查找换行符或 NUL 字符。

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_msgrecv_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

修改亚当 · 罗森菲尔德的代码:

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

然而,我极力鼓励使用原始的方法。

使用生成器函数的一种变体(我认为它更像 Python 语言) :

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))

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

这样应该更好

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


print "".join(fragments)

您可以使用 Serialization 进行此操作

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

大多数答案描述了某种 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

结果:

enter image description here

对于任何其他人谁正在寻找一个答案的情况下,你不知道的数据包长度之前。 下面是一个简单的解决方案,它一次读取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

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

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。一旦套接字接收到 b'\r\n',它就会中断循环。将 walrus_msg添加到 msg_list中,并在循环中断后打印。这个脚本是基本的,但是经过了测试,可以使用 telnet 会话。

注意: 需要在 (walrus_msg := con.recv(3))周围加上括号。如果不这样做,while walrus_msg := con.recv(3) != b'\r\n':walrus_msg计算为 True,而不是套接字上的实际数据。

免责声明: 在很少的情况下,你真的需要这样做。如果可能的话,使用现有的应用层协议或定义您自己的例子。在每条消息前面加上一个固定长度的整数,表示每条消息后面的数据长度,或者用’n’字符终止每条消息。(亚当•罗森菲尔德(Adam Rosenfield)的 回答非常好地解释了这一点)

也就是说,有一种方法可以读取套接字上的所有可用数据。然而,依赖这种通信是一个坏主意,因为它会带来数据丢失的风险。只有在阅读了下面的解释之后,才能极其谨慎地使用这个解决方案。

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 套接字一端的一个发送对应于另一端的一个接收发送的字节数。没有。有一个很好的解释问题 给你给你

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

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

jsonString = bytearray()


for _ in range(32):


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

数据驻留在 jsonString 变量中

简单明了:

data = b''
while True:
data_chunk = client_socket.recv(1024)
if data_chunk:
data+=data_chunk
else:
break