[英]Problem sending binary files via sockets, python
我正在嘗試編寫一個程序,將二進制文件從客戶端傳輸到服務器。 這是代碼:
客戶端(發送文件)
def send_file(self,filename):
print("Sending: " + filename)
size = self.BUFFER_SIZE
with open(filename,'rb') as f:
raw = f.read().decode()
buffer = [raw[i:i + size] for i in range(0, len(raw), size)]
for x in range(len(buffer)):
self.sock.sendall(buffer[x].encode())
return
服務器(recv文件)
def recv_file(self, conn, filename):
packet = ""
buffer = ""
while True:
buffer = conn.recv(self.BUFFER_SIZE)
packet = packet + str(buffer.decode())
if not len(buffer) == self.BUFFER_SIZE:
break
with open(filename, 'wb') as f:
f.write(bytes(packet.encode()))
#print(packet)
return
這樣我可以傳輸txt文件,但是當我必須傳輸jpeg或任何其他類型的文件時,它會在循環中凍結。 有人可以解釋一下為什么嗎? 我是py的新手,我正在努力學習
如果雙方都具有相同的區域設置編碼,它不應該凍結,但它很容易死於異常。
你正在閱讀和發送二進制(好),但莫名其妙地decode
為str
,然后encode
回bytes
(壞)。 問題是,任何二進制數據都不能保證在任何給定的語言環境中都是可解碼的; 如果你的語言環境編碼是UTF-8,則可能是合法的。 如果它是latin-1
它是合法的,但沒有意義。
更糟糕的是,如果您的客戶端和服務器具有不同的區域設置編碼,則每側的解碼結果可能不同(因此長度將不匹配)。
一致地使用bytes
,不要轉換為字符串和從字符串轉換,並且區域設置無關緊要。 您的代碼也會運行得更快。 您還需要提前實際發送文件長度; 你的循環希望recv
只在文件完成時返回一個很短的長度,但如果:
你可以得到簡短的recv
結果,在第2種情況下是巧合,在第1種情況下是確定性的。
更安全的方法是實際為傳輸添加文件長度前綴,而不是希望分塊按預期工作:
def send_file(self,filename):
print("Sending:", filename)
with open(filename, 'rb') as f:
raw = f.read()
# Send actual length ahead of data, with fixed byteorder and size
self.sock.sendall(len(raw).to_bytes(8, 'big'))
# You have the whole thing in memory anyway; don't bother chunking
self.sock.sendall(raw)
def recv_file(self, conn, filename):
# Get the expected length (eight bytes long, always)
expected_size = b""
while len(expected_size) < 8:
more_size = conn.recv(8 - len(expected_size))
if not more_size:
raise Exception("Short file length received")
expected_size += more_size
# Convert to int, the expected file length
expected_size = int.from_bytes(expected_size, 'big')
# Until we've received the expected amount of data, keep receiving
packet = b"" # Use bytes, not str, to accumulate
while len(packet) < expected_size:
buffer = conn.recv(expected_size - len(packet))
if not buffer:
raise Exception("Incomplete file received")
packet += buffer
with open(filename, 'wb') as f:
f.write(packet)
作為ShadowRanger帖子的附錄,如果你想在不使用socket.sendfile
情況下維護文件分塊,你可以利用一些技巧來清理你的代碼並減少內存占用。
發送過程非常簡單,因為我們復制了從ShadowRanger發送文件大小的過程,並添加了一個非常簡單的循環來發送數據塊,直到塊空出來(文件末尾)。
def send_file(self,filename):
print("Sending: " + filename)
#send file size as big endian 64 bit value (8 bytes)
self.sock.sendall(os.stat(filename).st_size.tobytes(8,'big'))
with open(filename,'rb') as f: #open our file to read
while True:
chunk = f.read(self.BUFFER_SIZE) #get next chunk
if not chunk: #empty chunk indicates EOF
break
self.sock.sendall(chunk) #send the chunk
接收文件也非常簡單,使用相同的過程在開始時讀取預期的文件大小,然后在我們達到預期大小之前將數據讀入該文件。 然后我們使用f.tell()
作為接收數據,這是一種告訴整個文件是否已經發送的簡單方法。
def recv_file(self, conn, filename):
# file size transfer copied from ShadowRanger
# Get the expected length (eight bytes long, always)
expected_size = b"" #buffer to read in file size
while len(expected_size) < 8: #while buffer is smaller than 8 bytes
more_size = conn.recv(8 - len(expected_size)) #read up to remaining bytes
if not more_size: #nothing was read
raise Exception("Short file length received")
expected_size += more_size #extend buffer
expected_size = int.from_bytes(expected_size, 'big') #Convert to int, the expected file length
with open(filename, 'wb') as f: #open our file to write
while f.tell() < expected_size: #while it's smaller than our expected size
bytes_recvd = conn.recv() #read any available data
f.write(bytes_recvd)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.