Python Socket传输大字符串超时,寻求更优数据发送方案
大文件Socket传输超时问题,求优化方案
我编写了receive_file函数,通过Socket向服务器发送包含TCL命令的Base64编码字符串。100KB及以下的小文件传输无异常,但传输更大文件时会出现超时问题。请问是否有更优的数据发送方式?
def connect(self): self.S = socket.socket() # Create socket (for TCP) self.S.connect((self.host, self.port)) # Connect to aRTist self.S.settimeout(self.timeout) self.listen(0) return self def send(self, command, msgType="RESULT"): c = command + '\n' self.S.sendall(c.encode()) return self.listen(msgType=msgType) def listen(self, command_no=1, msgType="RESULT"): answer = "" stop = False if (command_no == 0): self.S.settimeout(0.2) while (not stop):# and ("SUCCESS" not in total) and ("ERROR" not in total): # Solange server antwortet und nicht "SUCCESS" enthält try: msg = self.S.recv(self.bufferSize).decode() except BaseException as e: err = e.args[0] if err == "timed out": #print("Timeout\n") answer += "RESULT Timeout\n" stop = True continue else: if ("SUCCESS" in msg): answer += msg stop = True continue elif ("ERROR" in msg): answer += msg stop = True #global error self.error = self.error + 1 continue elif ("PROGRESS" in msg): try: self.progress = float(msg.strip('PROGRESS ')) except: self.progress = 0 continue else: if (command_no == 0): print(msg) answer += msg self.S.settimeout(self.timeout) if (msgType != "*"): answer = self.pick(answer, msgType) self.answer.update({"SUCCESS":self.pick(answer, "SUCCESS"), "RESULT":self.pick(answer, "RESULT"), "SDTOUT":self.pick(answer, "STDOUT"), "BASE64":self.pick(answer, "BASE64"), "IMAGE":self.pick(answer, "IMAGE"), "FILE":self.pick(answer, "FILE")}) return answer def pick(self, answer, res='RESULT'): picked = "" for a in answer.split('\n'): if a.find(res) == 0: picked += (a[1 + len(res):].strip('\r') + '\n') if len(picked) == 0: return res + ' not found.' return picked def image(self, imageName): self.lst.clear() cTypes = ["bit", "char", "signed char", "unsigned char", "short", "unsigned short", "int", "unsigned int", "long", "unsigned long", "float", "double"] npTypes = [np.bool_, np.ubyte, np.byte, np.ubyte, np.short, np.ushort, np.intc, np.uintc, np.int_, np.uint, np.single, np.double] imageData = self.answer["BASE64"] decodedData = base64.b64decode((imageData)) imageHeader = self.answer["IMAGE"] for i in imageHeader.split(","): self.lst.append(i) imType = self.lst[4] if imType in cTypes: dtype = npTypes[cTypes.index(imType)] im = np.frombuffer(decodedData, dtype).reshape((int(self.lst[1]),int(self.lst[2]))) Image.fromarray(im).save(imageName) def send_file(self, fileName): fileData = self.answer["BASE64"] decodedFile = base64.b64decode((fileData)) artistFile = open(fileName, "wb") artistFile.write(decodedFile) artistFile.close() def receive_file(self, fileName2): outFile = open(fileName2, "br") fileBytes = outFile.read() encBytes = base64.b64encode((fileBytes)) encString = str(encBytes) encString2 = encString.lstrip("b'") encString3 = encString2.rstrip("'") com = "::RemoteControl::ReceiveFile " + encString3 + " .aRTist" recAnswer = self.send(com, "RESULT") return recAnswer
问题根源
当前实现把大文件的Base64编码内容直接拼接成单条TCL命令发送,会导致:
- 单条TCP数据包过大,触发网络分片,增加传输失败概率
- 服务器处理超大命令字符串时易阻塞,无法及时返回响应引发客户端超时
- Base64编码会让数据体积膨胀33%,进一步加剧传输压力
优化方案
1. 分块传输文件内容
拆分文件为固定大小的块,分多次发送,配合服务器端的分块接收逻辑:
import os def receive_file(self, fileName2, chunk_size=4096): with open(fileName2, "br") as outFile: # 先发送文件总大小,通知服务器准备接收 file_size = os.path.getsize(fileName2) self.send(f"::RemoteControl::InitReceiveFile {file_size} .aRTist") while True: file_bytes = outFile.read(chunk_size) if not file_bytes: break # 对块进行Base64编码并发送 enc_string = base64.b64encode(file_bytes).decode('utf-8') self.send(f"::RemoteControl::ReceiveFileChunk {enc_string} .aRTist") # 发送传输结束标记 return self.send("::RemoteControl::FinishReceiveFile .aRTist", "RESULT")
需服务器端对应实现InitReceiveFile、ReceiveFileChunk、FinishReceiveFile三个TCL命令,负责接收块并拼接成完整文件
2. 跳过Base64,直接发送二进制数据
Base64会额外增加数据体积,若服务器支持直接接收二进制,可优化为:
import os def receive_file(self, fileName2, chunk_size=8192): with open(fileName2, "br") as outFile: # 发送文件头信息(大小),用\x00分隔命令与二进制数据 file_size = os.path.getsize(fileName2) header = f"::RemoteControl::ReceiveBinaryFile {file_size} .aRTist\x00".encode() self.S.sendall(header) # 直接发送二进制文件块 while True: chunk = outFile.read(chunk_size) if not chunk: break self.S.sendall(chunk) # 发送结束标记 self.S.sendall(b"\x00\x00") return self.listen("RESULT")
需服务器端修改接收逻辑,先读取命令头,再接收后续二进制数据
3. 优化超时与监听逻辑
当前listen函数的超时设置在大文件传输时可能过早触发:
- 移除
command_no=0时的0.2秒短超时,改为根据文件大小动态设置更长超时 - 优化循环条件,累计接收内容直到匹配
SUCCESS/ERROR标记,避免单次recv无结果就终止
4. 简化Base64字符串处理
若必须保留Base64编码,可简化字符串操作,避免不必要的性能损耗:
# 替代原代码中str(encBytes)再strip的操作 enc_string = base64.b64encode(fileBytes).decode('utf-8')
内容的提问来源于stack exchange,提问作者daveidix
相关产品推荐
相关产品推荐

