在 Python 中通过套接字发送大量数据的正确方法是啥?
Posted
技术标签:
【中文标题】在 Python 中通过套接字发送大量数据的正确方法是啥?【英文标题】:What is the proper way of sending a large amount of data over sockets in Python?在 Python 中通过套接字发送大量数据的正确方法是什么? 【发布时间】:2017-02-25 17:49:21 【问题描述】:最近我写了一些代码(客户端和服务器)来发送图像 - 客户端只需使用socket
模块将图像上传到服务器:Sending image over sockets (ONLY) in Python, image can not be open。
但是,我现在关心的是图像发送部分。这是我正在使用的原始图像:
在我的服务器代码(接收图像)中,我有以下几行:
myfile = open(basename % imgcounter, 'wb')
myfile.write(data)
data = sock.recv(40960000)
if not data:
myfile.close()
break
myfile.write(data)
myfile.close()
sock.sendall("GOT IMAGE")
sock.shutdown()
但我认为这不是最好的方法。我认为我应该改为实现服务器,以便它以块的形式接收数据:
#!/usr/bin/env python
import random
import socket, select
from time import gmtime, strftime
from random import randint
imgcounter = 1
basename = "image%s.png"
HOST = '127.0.0.1'
PORT = 2905
connected_clients_sockets = []
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen(10)
connected_clients_sockets.append(server_socket)
while True:
read_sockets, write_sockets, error_sockets = select.select(connected_clients_sockets, [], [])
for sock in read_sockets:
if sock == server_socket:
sockfd, client_address = server_socket.accept()
connected_clients_sockets.append(sockfd)
else:
try:
data = sock.recv(4096)
txt = str(data)
if data:
if data.startswith('SIZE'):
tmp = txt.split()
size = int(tmp[1])
print 'got size %s' % size
sock.sendall("GOT SIZE")
elif data.startswith('BYE'):
sock.shutdown()
else :
myfile = open(basename % imgcounter, 'wb')
myfile.write(data)
amount_received = 0
while amount_received < size:
data = sock.recv(4096)
amount_received += len(data)
print amount_received
if not data:
break
myfile.write(data)
myfile.close()
sock.sendall("GOT IMAGE")
sock.shutdown()
except:
sock.close()
connected_clients_sockets.remove(sock)
continue
imgcounter += 1
server_socket.close()
但是当我这样做时,服务器会打印:
got size 54674
4096
8192
12288
16384
20480
24576
28672
32768
36864
40960
45056
49152
50578
然后似乎挂起,客户端也挂起。但是,在服务器端,我只能看到客户端想要发送的图像的一部分:
似乎缺少一些字节。仅使用套接字发送大量数据(图像、其他类型的文件)的正确方法是什么?
【问题讨论】:
您使用的是select
,但也使用了recv
的内部while 循环。这没有任何意义。
@Daniel:事实并非如此。在我链接的帖子中,客户端和服务器都可以工作,而且我有(几乎)准确的代码。
我无法阅读 Python,但如果数据以任意大小的块(确实如此)到达,那么您无法明智地检查数据是否以“SIZE”开头,因为那可能不在确定开始?
这个问题可能对你有帮助:***.com/questions/8994937/…
【参考方案1】:
我假设您有特定的原因使用裸套接字执行此操作,例如自我教育,这意味着我不会回答说“您不小心忘记使用 HTTP 和 Twisted”,这可能你有heard before :-P。但实际上,您应该在某个时候查看更高级别的库,因为它们要容易得多!
定义协议
如果您只想发送图像,那么它可以很简单:
Client -> server: 8 bytes
: 大端,图片长度。
Client -> server: length bytes
:所有图片数据。
(Client <- server: 1 byte, value 0
:表示已收到传输 - 可选步骤,您可能不在乎是否使用 TCP,只是假设它是可靠的。)
编码
server.py
import os
from socket import *
from struct import unpack
class ServerProtocol:
def __init__(self):
self.socket = None
self.output_dir = '.'
self.file_num = 1
def listen(self, server_ip, server_port):
self.socket = socket(AF_INET, SOCK_STREAM)
self.socket.bind((server_ip, server_port))
self.socket.listen(1)
def handle_images(self):
try:
while True:
(connection, addr) = self.socket.accept()
try:
bs = connection.recv(8)
(length,) = unpack('>Q', bs)
data = b''
while len(data) < length:
# doing it in batches is generally better than trying
# to do it all in one go, so I believe.
to_read = length - len(data)
data += connection.recv(
4096 if to_read > 4096 else to_read)
# send our 0 ack
assert len(b'\00') == 1
connection.sendall(b'\00')
finally:
connection.shutdown(SHUT_WR)
connection.close()
with open(os.path.join(
self.output_dir, '%06d.jpg' % self.file_num), 'w'
) as fp:
fp.write(data)
self.file_num += 1
finally:
self.close()
def close(self):
self.socket.close()
self.socket = None
# could handle a bad ack here, but we'll assume it's fine.
if __name__ == '__main__':
sp = ServerProtocol()
sp.listen('127.0.0.1', 55555)
sp.handle_images()
client.py
from socket import *
from struct import pack
class ClientProtocol:
def __init__(self):
self.socket = None
def connect(self, server_ip, server_port):
self.socket = socket(AF_INET, SOCK_STREAM)
self.socket.connect((server_ip, server_port))
def close(self):
self.socket.shutdown(SHUT_WR)
self.socket.close()
self.socket = None
def send_image(self, image_data):
# use struct to make sure we have a consistent endianness on the length
length = pack('>Q', len(image_data))
# sendall to make sure it blocks if there's back-pressure on the socket
self.socket.sendall(length)
self.socket.sendall(image_data)
ack = self.socket.recv(1)
# could handle a bad ack here, but we'll assume it's fine.
if __name__ == '__main__':
cp = ClientProtocol()
image_data = None
with open('IMG_0077.jpg', 'r') as fp:
image_data = fp.read()
assert(len(image_data))
cp.connect('127.0.0.1', 55555)
cp.send_image(image_data)
cp.close()
【讨论】:
非常感谢。但是如果我想对消息填充做同样的事情,它应该看起来像这样:(服务器gist.github.com/anonymous/f349ab3ab963295baa6435ad9169e5da)和(客户端:gist.github.com/anonymous/a327124e0e82b748a04fbf468bd165fb)?我的意思是,您的代码非常好,但我宁愿使用消息填充。问题是,我不知道我实现的对不对。 我不太明白您所说的“我宁愿使用消息填充”是什么意思。您能否解释一下您希望协议看起来像什么(也许只是更新问题)?使我的代码适应稍微不同的协议应该很容易。我认为您发布链接的代码也存在一些问题,但如果您想要进行代码审查,您应该在codereview.stackexchange 上进行。无论如何,你问“正确”的方式是什么,我想我已经回答了! 通过消息填充我的意思是我在链接代码中所做的 - 以特定分隔符结束消息,而不是像您那样以消息的大小开始它们。我想实现的协议是gist.github.com/anonymous/2dc52dc5288c85831dd370a85d255903。谢谢。 但是问题是你不能使用基于分隔符的协议来发送任意数据,因为如果分隔符出现在数据中会发生什么? jpeg 数据基本上是随机的。所以无论如何你都必须发送一个长度数字,这就是你正在做的事情。但是,如果您的算法既基于分隔符对数据进行分区,又根据最后一条消息使用长度参数,那将变得更加复杂!此外,您链接到的协议是让客户端从服务器检索文件,但这个问题是让客户端将文件发送到服务器...... 我的另一个建议是分块读取/发送数据【参考方案2】:一种简单的方法是将数据大小作为数据的前 4 个字节发送,然后一次性读取完整的数据。在客户端和服务器端使用以下函数来发送和接收数据。
def send_data(conn, data):
serialized_data = pickle.dumps(data)
conn.sendall(struct.pack('>I', len(serialized_data)))
conn.sendall(serialized_data)
def receive_data(conn):
data_size = struct.unpack('>I', conn.recv(4))[0]
received_payload = b""
reamining_payload_size = data_size
while reamining_payload_size != 0:
received_payload += conn.recv(reamining_payload_size)
reamining_payload_size = data_size - len(received_payload)
data = pickle.loads(received_payload)
return data
你可以在https://github.com/vijendra1125/Python-Socket-Programming.git找到示例程序
【讨论】:
【参考方案3】:问题是你没有为接收到的第一块数据增加amount_received
。
修复如下:
#!/usr/bin/env python
import random
import socket, select
from time import gmtime, strftime
from random import randint
imgcounter = 1
basename = "image%s.png"
HOST = '127.0.0.1'
PORT = 2905
connected_clients_sockets = []
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen(10)
connected_clients_sockets.append(server_socket)
while True:
read_sockets, write_sockets, error_sockets = select.select(connected_clients_sockets, [], [])
for sock in read_sockets:
if sock == server_socket:
sockfd, client_address = server_socket.accept()
connected_clients_sockets.append(sockfd)
else:
try:
data = sock.recv(4096)
txt = str(data)
if data:
if data.startswith('SIZE'):
tmp = txt.split()
size = int(tmp[1])
print 'got size %s' % size
sock.sendall("GOT SIZE")
elif data.startswith('BYE'):
sock.shutdown()
else :
myfile = open(basename % imgcounter, 'wb')
myfile.write(data)
amount_received = len(data) # The fix!
while amount_received < size:
data = sock.recv(4096)
amount_received += len(data)
print amount_received
if not data:
break
myfile.write(data)
myfile.close()
sock.sendall("GOT IMAGE")
sock.shutdown()
except:
sock.close()
connected_clients_sockets.remove(sock)
continue
imgcounter += 1
server_socket.close()
【讨论】:
以上是关于在 Python 中通过套接字发送大量数据的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 c++ 中通过套接字(发送函数)发送图片,但不接收完整(Windows)!