You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:45:00