Python客户端向服务器传输文件及文件夹时接收异常求解决方案
Python Socket 文件/文件夹传输服务器接收异常解决方案
我编写了一个Python程序,用于将客户端的文件及文件夹复制到服务器,但目前服务器接收数据时出现异常。推测问题在于客户端在服务器完成文件与目录结构构建前就发送了全部数据,求解决方案。
服务器端原始代码
import socket import os import tqdm def receive_file(c, name, path, file_size): # 接收数据并在存储中创建本地文件 new_path = path + "/" + name with open(new_path, "wb") as f: bytes_read = c.recv(file_size) f.write(bytes_read) def receive_folder(c, name, num_in, path): new_path = path + "/" + name os.mkdir(new_path) for i in range(num_in): item = c.recv(128).decode("utf-8") item = item.split("$") check = item[0] name1 = item[1] size = int(item[2]) if check == "folder": receive_folder(c, name1, num_in, new_path) else: receive_file(c, name1, new_path, size) def main(): # 创建套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 8080)) s.listen() while True: # 接受客户端连接 c, address = s.accept() print("client connected...") c.send(bytes("connection successful...", "utf-8")) msg = c.recv(1024).decode("utf-8") msg = msg.split("$") check = msg[0] name = msg[1] num_in = int(msg[2]) path = r"storage" # 判断是文件还是文件夹 if check == "folder": receive_folder(c, name, num_in, path) else: receive_file(c, name, path, num_in) print("received :-)") if __name__ == '__main__': main()
客户端原始代码
import socket import os def is_folder(file_name): if os.path.isdir(r"storage/" + file_name): return True else: return False def send_file(s, file_name, path): new_path = path + "/" + file_name file_size = os.path.getsize(new_path) s.send(("file" + "$" + file_name + "$" + str(file_size)).encode("utf-8")) with open(new_path, "rb") as f: while True: data = f.read(4096) if not data: break s.send(data) def send_folder(s, folder_name, path): new_path = path + "/" + folder_name content = os.listdir(new_path) content_num = len(content) s.send(("folder" + "$" + folder_name + "$" + str(content_num)).encode("utf-8")) for i in range(content_num): if os.path.isfile(new_path + "/" + content[i]): send_file(s, content[i], new_path) elif os.path.isdir(new_path + "/" + content[i]): send_folder(s, content[i], new_path) def main(): # 创建套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 8080)) msg = s.recv(1024).decode() print(msg) # 获取要传输的对象名称 name = str(input("enter name:")) path = r"storage" # 判断传输对象是文件还是文件夹并告知服务器 if is_folder(name): send_folder(s, name, path) else: send_file(s, name, path) print("Data Sent Successfully :-)") if __name__ == '__main__': main()
核心问题分析
- TCP粘包:客户端连续发送的元数据与文件内容会被TCP合并,导致服务器
recv时解析出错误的分割结果。 - 递归参数错误:服务器
receive_folder递归调用时,错误地将父文件夹的内容数量传入子文件夹处理逻辑,导致循环次数错误。 - 文件接收逻辑缺陷:直接用
recv(file_size)一次性接收文件,大文件场景下无法保证一次接收完整,且未处理粘包带来的多余数据。 - 无确认机制:客户端发送数据前未等待服务器的准备确认,导致服务器还未完成目录结构创建就收到数据,解析混乱。
解决方案及修正代码
关键优化点
- 加入确认机制:客户端发送元数据后等待服务器确认,确保服务器准备就绪再发送后续数据。
- 修复递归参数错误:服务器递归处理子文件夹时,动态接收子文件夹的内容数量。
- 分块接收文件:按固定大小分块接收文件数据,直到接收字节数达到文件大小,避免一次性接收的局限性。
- 使用
os.path.join拼接路径,提升跨平台兼容性。
修正后的服务器端代码
import socket import os def receive_file(c, name, path, file_size): new_path = os.path.join(path, name) received_bytes = 0 with open(new_path, "wb") as f: while received_bytes < file_size: # 每次接收不超过4096字节,避免内存占用过大 chunk = c.recv(min(4096, file_size - received_bytes)) if not chunk: break f.write(chunk) received_bytes += len(chunk) # 发送文件接收完成确认 c.send(b"FILE_OK") def receive_folder(c, name, path): new_path = os.path.join(path, name) # 确保目录存在,避免重复创建报错 os.makedirs(new_path, exist_ok=True) # 接收子项数量 num_in = int(c.recv(128).decode("utf-8").strip()) c.send(b"NUM_OK") for _ in range(num_in): # 接收子项元数据 item_meta = c.recv(256).decode("utf-8").strip() item = item_meta.split("$") check = item[0] name1 = item[1] if check == "folder": # 告知客户端可发送子文件夹信息 c.send(b"FOLDER_OK") receive_folder(c, name1, new_path) else: size = int(item[2]) # 告知客户端可发送文件内容 c.send(b"FILE_META_OK") receive_file(c, name1, new_path, size) def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((socket.gethostname(), 8080)) s.listen() while True: c, address = s.accept() print("client connected...") c.send(b"connection successful...") # 接收根对象元数据 root_meta = c.recv(256).decode("utf-8").strip() msg = root_meta.split("$") check = msg[0] name = msg[1] path = r"storage" os.makedirs(path, exist_ok=True) if check == "folder": c.send(b"ROOT_FOLDER_OK") receive_folder(c, name, path) else: size = int(msg[2]) c.send(b"ROOT_FILE_OK") receive_file(c, name, path, size) print("received :-)") c.close() if __name__ == '__main__': main()
修正后的客户端代码
import socket import os def is_folder(file_name): return os.path.isdir(os.path.join(r"storage", file_name)) def send_file(s, file_name, path): new_path = os.path.join(path, file_name) file_size = os.path.getsize(new_path) # 发送文件元数据 s.send(f"file${file_name}${file_size}".encode("utf-8")) # 等待服务器确认已准备接收文件 s.recv(1024) # 分块发送文件内容 with open(new_path, "rb") as f: while True: data = f.read(4096) if not data: break s.send(data) # 等待服务器确认文件接收完成 s.recv(1024) def send_folder(s, folder_name, path): new_path = os.path.join(path, folder_name) content = os.listdir(new_path) content_num = len(content) # 发送文件夹标识与名称 s.send(f"folder${folder_name}".encode("utf-8")) # 等待服务器确认 s.recv(1024) # 发送子项数量 s.send(str(content_num).encode("utf-8")) s.recv(1024) for item in content: item_path = os.path.join(new_path, item) if os.path.isfile(item_path): send_file(s, item, new_path) elif os.path.isdir(item_path): send_folder(s, item, new_path) def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((socket.gethostname(), 8080)) msg = s.recv(1024).decode() print(msg) name = input("enter name:") path = r"storage" if is_folder(name): send_folder(s, name, path) else: send_file(s, name, path) print("Data Sent Successfully :-)") s.close() if __name__ == '__main__': main()
内容的提问来源于stack exchange,提问作者Amit Grimberg
相关产品推荐
相关产品推荐

