FTP上传实例(带进度条):异步IO与断点续传实战
简介这是一份面向C#开发者的FTP文件上传实例源码聚焦于在文件传输过程中加入可视化进度反馈这一常见需求。资源以FtpProject工程形式组织包含完整的解决方案与项目配置便于直接编译运行或移植到自己的项目中。压缩包共35个文件以cs源码文件为主辅以resx资源、config配置、exe可执行程序及pdb调试符号等整体约78KB体量轻巧但结构完整。代码中封装了FTP连接、登录、切换目录与文件上传等核心方法并通过回调机制跟踪已传字节数与总字节数的比例实时驱动进度条更新直观呈现上传进度。目前已有868人学习下载适合正在学习网络编程、需要为上传功能补充进度提示的开发者参考也可作为FTP帮助类复用到其他项目帮助理解分块传输、进度计算与连接释放等关键环节。1. FTP上传实例带进度条为什么你传大文件时总想砸键盘做过桌面工具或内部运维面板的人大概率都写过 FTP 上传。功能跑通不难难的是让用户在上传 2GB 安装包时界面不假死、进度条不卡在 99%、断线后还能接着传。我见过太多项目上传按钮点下去窗口直接白屏用户以为程序崩了其实只是主线程被同步 IO 堵死了。FTP上传实例带进度条这个标题核心不是 FTP 协议本身而是把不可见的字节流动变成可见的进度反馈同时保证传输稳定。它适合两类人一是需要给内部系统加文件上传功能的开发者二是想理解「异步 IO 进度回调」这套组合拳的工程师。下面我按实际落地顺序从协议选型讲到断点续传把踩过的坑一个个摊开。2. 先搞懂 FTP 的两种连接模式别让数据通道成为黑匣子2.1 控制连接与数据连接的分工FTP 和 HTTP 最大的区别在于它用两条 TCP 连接一条控制连接默认 21 端口负责发指令、收响应码另一条数据连接专门传文件内容。控制连接在整个会话期间保持数据连接每次传输时临时建立。很多人写上传时只盯着文件流忽略了控制连接的响应解析结果服务器返回150 Opening data connection之后代码还在傻等进度条自然不动。常见做法是控制连接用阻塞 socket 收响应数据连接用独立线程或异步任务发送字节。每发送一个数据块就更新一次进度回调。这里的关键是不要把控制连接和数据连接放在同一个线程里串行等待否则一个STOR指令发出去数据连接还没建好界面就卡住了。2.2 主动模式与被动模式的选型理由主动模式PORT是服务器主动连客户端的数据端口被动模式PASV是客户端连服务器的数据端口。现在绝大多数客户端和服务器默认走被动模式因为主动模式在 NAT 环境下基本不可用。我一般会强制指定 PASV并在代码里显式发送PASV指令解析返回的 IP 和端口。如果服务器返回的 IP 是内网地址比如 10.x.x.x而你的客户端在外网就需要忽略服务器返回的 IP直接用控制连接的远端 IP 加返回的端口。这个坑在云服务器上特别常见现象是数据连接超时控制连接却正常。from ftplib import FTP import socket ftp FTP() ftp.connect(host, 21, timeout10) ftp.login(user, passwd) ftp.set_pasv(True) # 强制被动模式 # 发送 PASV 并解析 resp ftp.sendcmd(PASV) # 响应格式: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) import re nums list(map(int, re.search(r\((\d,\d,\d,\d,\d,\d)\), resp).group(1).split(,))) data_host ..join(map(str, nums[:4])) data_port nums[4] * 256 nums[5] # 如果返回内网 IP替换为控制连接的远端 IP if data_host.startswith(10.) or data_host.startswith(192.168.): data_host ftp.sock.getpeername()[0]这段代码先建立控制连接然后手动发PASV指令解析数据端口。参数timeout10是控制连接超时数据连接要单独设。set_pasv(True)只是告诉 ftplib 后续用被动模式但解析端口还是得自己做因为不同服务器返回格式可能有细微差异。注意ftp.sock.getpeername()拿到的是控制连接的远端地址用它替换内网 IP 能解决大部分云服务器数据连接失败的问题。2.3 用 ftplib 的 storbinary 回调做进度条Python 标准库ftplib的storbinary方法支持一个callback参数每发送一个数据块就会调用一次。这是最省事的进度条实现方式不需要自己管理数据连接。import os from ftplib import FTP def upload_with_progress(ftp, local_path, remote_path): total_size os.path.getsize(local_path) uploaded 0 def callback(chunk): nonlocal uploaded uploaded len(chunk) percent uploaded / total_size * 100 # 这里更新 UI 进度条注意线程安全 print(f\r进度: {percent:.1f}% ({uploaded}/{total_size}), end) with open(local_path, rb) as f: ftp.storbinary(fSTOR {remote_path}, f, blocksize8192, callbackcallback)blocksize8192是每次发送的字节数设太小回调太频繁拖慢速度设太大进度条跳动明显。我一般用 8192 到 65536 之间根据网络延迟调整。callback收到的chunk是实际发送的字节块累加后除以总大小就是进度。注意nonlocal uploaded在 Python 3 里才能用Python 2 需要改成可变对象。另外这个回调是在发送线程里执行的如果 UI 在主线程必须用queue或信号槽把进度传过去否则会崩。3. 把进度条做稳异步、分块与断点续传的落地细节3.1 为什么同步上传会让界面假死同步上传时storbinary会一直阻塞直到文件传完。如果文件几百兆主线程被占满Windows 消息循环得不到处理界面就显示「无响应」。解决办法是把上传任务放到独立线程主线程只负责刷新进度。但线程不能直接操作 UI 控件常见做法是用queue.Queue传进度值主线程定时轮询。import threading import queue import time progress_queue queue.Queue() def upload_thread(local_path, remote_path): ftp FTP() ftp.connect(host, 21, timeout10) ftp.login(user, passwd) ftp.set_pasv(True) total_size os.path.getsize(local_path) uploaded 0 def callback(chunk): nonlocal uploaded uploaded len(chunk) progress_queue.put(uploaded / total_size * 100) with open(local_path, rb) as f: ftp.storbinary(fSTOR {remote_path}, f, blocksize8192, callbackcallback) ftp.quit() progress_queue.put(done) # 主线程轮询 def poll_progress(): while True: try: val progress_queue.get_nowait() if val done: break # 更新进度条控件 progress_bar.setValue(int(val)) except queue.Empty: pass time.sleep(0.1)线程里创建独立的 FTP 连接避免和控制连接冲突。progress_queue是线程安全的主线程每 100ms 取一次值更新界面。注意ftp.quit()要放在with块外面确保文件关闭后再发 QUIT。如果上传中途出错要在except里把异常也塞进队列主线程好弹窗提示。3.2 分块上传与进度计算的精度问题进度条卡在 99% 是经典翻车场景。原因通常是最后一个数据块发送后服务器还在写磁盘控制连接没返回226 Transfer complete但回调已经算到 100% 了。解决办法是进度只算到 99%等收到 226 响应码再置 100%。另外storbinary的 callback 是在每个块发送后调用但 TCP 缓冲区可能还没刷出去所以进度是「已提交到 socket」而非「已落盘」。对精度要求高的场景可以用ftp.voidcmd(NOOP)探测服务器是否还在处理。def upload_with_accurate_progress(ftp, local_path, remote_path): total_size os.path.getsize(local_path) uploaded 0 def callback(chunk): nonlocal uploaded uploaded len(chunk) # 最大只显示 99% percent min(uploaded / total_size * 100, 99) update_ui(percent) with open(local_path, rb) as f: ftp.storbinary(fSTOR {remote_path}, f, blocksize8192, callbackcallback) # 传输完成收到 226 后置 100% update_ui(100)min(..., 99)是保险丝防止回调提前算满。storbinary返回后控制连接已经收到 226 响应此时置 100% 才准确。如果服务器响应慢可以在storbinary后面加一个ftp.voidcmd(NOOP)确认连接还活着。3.3 断点续传APPE 指令与本地偏移量记录大文件传到一半断网重新传不能从头开始。FTP 的APPE指令可以在远程文件末尾追加数据配合本地记录已传字节数就能实现断点续传。但APPE有个坑如果远程文件不存在它会创建新文件如果存在它追加。所以续传前要先SIZE查远程文件大小和本地已传大小比对。def resume_upload(ftp, local_path, remote_path): local_size os.path.getsize(local_path) try: remote_size ftp.size(remote_path) except Exception: remote_size 0 if remote_size local_size: print(远程文件已完整跳过) return with open(local_path, rb) as f: f.seek(remote_size) # 跳过已传部分 ftp.storbinary(fAPPE {remote_path}, f, blocksize8192, callbacklambda chunk: update_ui(...))ftp.size()返回远程文件字节数如果远程比本地大说明本地文件被改过直接报错。f.seek(remote_size)把文件指针移到断点处APPE从该位置继续追加。注意APPE不是所有服务器都支持遇到不支持的就只能重新传。另外本地要维护一个.uploading状态文件记录 remote_path 和已传大小程序重启后才能恢复。4. 避坑指南FTP 上传进度条的五个血泪教训4.1 现象进度条瞬间到 100%但文件没传完原因storbinary的 callback 是在数据写入 socket 缓冲区后调用不是服务器确认收到。如果网络快缓冲区瞬间填满回调就飙到 100%。解决用min(percent, 99)限制等storbinary返回后再置 100%。4.2 现象上传大文件时程序内存暴涨原因一次性把整个文件读进内存再发送。storbinary默认按块读但如果你自己实现发送逻辑用了f.read()不带参数就会读整个文件。解决始终用f.read(blocksize)分块读blocksize控制在 64KB 以内。4.3 现象被动模式下数据连接超时控制连接正常原因服务器返回的 PASV IP 是内网地址客户端在外网连不上。解决解析 PASV 响应后如果 IP 是 10.x、192.168.x、172.16.x 开头替换为控制连接的远端 IP。代码见 2.2 节。4.4 现象上传中文文件名乱码原因FTP 协议默认用 ASCII 编码中文文件名需要 UTF-8。解决连接后发送OPTS UTF8 ON并在storbinary里用remote_path.encode(utf-8)。如果服务器不支持 UTF8就只能用英文文件名。4.5 现象断点续传后文件损坏原因APPE追加时本地文件被其他程序修改导致偏移量错位。解决续传前用os.path.getmtime和getsize双重校验或者在上传期间锁定本地文件。更稳妥的做法是每次续传前重新计算 MD5但大文件 MD5 很慢我一般只校验大小和修改时间。5. 进阶技巧用 ftplib 的底层 socket 做自定义进度回调标准storbinary的 callback 粒度受blocksize限制如果你想做更细的进度比如每 1KB 更新一次或者想在发送过程中动态调整块大小可以直接操作数据连接 socket。下面这个例子用ftp.transfercmd拿到数据连接自己控制发送循环。def custom_upload(ftp, local_path, remote_path, chunk_size4096): total_size os.path.getsize(local_path) uploaded 0 # 建立数据连接 conn ftp.transfercmd(fSTOR {remote_path}) with open(local_path, rb) as f: while True: chunk f.read(chunk_size) if not chunk: break conn.sendall(chunk) uploaded len(chunk) percent uploaded / total_size * 100 update_ui(percent) conn.close() ftp.voidresp() # 读取 226 响应ftp.transfercmd返回一个 socket 对象直接sendall发送数据。chunk_size可以动态调整网络好时用 64KB网络差时降到 4KB 减少丢包重传。ftp.voidresp()必须调用否则控制连接的下一个指令会读到残留响应。这个方法的缺点是失去了storbinary的自动错误处理需要自己捕获socket.error并重连。另一个技巧是用ftplib的FTP子类重写storbinary在发送循环里插入进度回调。但我不建议这么做因为标准库的实现已经处理了边界情况自己重写容易漏掉ABOR指令和超时处理。除非你有特殊需求否则用transfercmd就够了。最后说一个我自己的习惯每次上传前先用ftp.sendcmd(TYPE I)切换到二进制模式避免服务器对文件做换行转换。这个坑在传压缩包时特别明显现象是上传后的 zip 解压报错但文件大小看起来正常。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

VCam_v5.0虚拟摄像头驱动:WDM内核级实现与sn签名激活方案

VCam_v5.0虚拟摄像头驱动:WDM内核级实现与sn签名激活方案

简介:本资源是面向Windows平台用户的虚拟摄像头工具VCam v5.0完整安装包,适用于在线会议、直播推流、网课教学及隐私保护等场景,尤其适合不熟悉注册流程但需即装即用的中初级用户。压缩包共5个文件,含核心可执行程序VCam_v5.0.exe…

2026/10/12 2:37:28 阅读更多 →
JavaWeb项目源码启动指南:从解压到首屏访问的完整路径

JavaWeb项目源码启动指南:从解压到首屏访问的完整路径

简介:这是一份面向Java Web初学者与中级开发者的完整分页打印功能实战项目源码,适用于高校课程设计、企业内部培训及Web前端与后端协同开发练习。项目基于JSPServletJDBC技术栈,实现从数据库查询数据、在网页表格中动态展示、支持服务端分页、…

2026/10/12 2:37:28 阅读更多 →
SketchUp插件怎么装才不卡?从RBZ安装到工作流固化指南

SketchUp插件怎么装才不卡?从RBZ安装到工作流固化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 2:37:28 阅读更多 →

最新新闻

PostgreSQL性能压测实战:用TPC-H标准流程构建可复现基准测试环境

PostgreSQL性能压测实战:用TPC-H标准流程构建可复现基准测试环境

1. 项目概述:为什么TPC-H是检验PostgreSQL真实能力的“压力测试仪”你刚装好PostgreSQL,跑通了第一个CREATE TABLE,连上pgAdmin点了几次查询,心里有点小得意——数据库这玩意儿,好像也没那么难?别急&#x…

2026/10/12 5:09:00 阅读更多 →
基于Spring Boot的车牌识别停车场管理系统设计与实现

基于Spring Boot的车牌识别停车场管理系统设计与实现

1. 项目概述与选题价值1.1 这个系统到底解决什么问题我第一次看到这个题目的时候,第一反应是:这又是一个“典型的毕业设计式管理系统”?因为现在网上关于停车场、图书馆、宿舍管理这类CRUD项目太多了,很多同学开题时随手挑一个&am…

2026/10/12 5:09:00 阅读更多 →
Spring Boot农事管理系统毕业设计:从数据库建模到核心功能实现

Spring Boot农事管理系统毕业设计:从数据库建模到核心功能实现

写这个题目前,我先说句实在话:Spring Boot 农事管理系统,这个搭配在国内农业信息化方向的毕业设计里,已经算得上“经典款”了。经典意味着什么?意味着参考资料好找、技术路线成熟、踩坑记录也很多,不至于让…

2026/10/12 5:09:00 阅读更多 →
MATLAB快速谱相干:从一维时间序列到旋转机械多通道分析

MATLAB快速谱相干:从一维时间序列到旋转机械多通道分析

前几天我在一个设备诊断交流群里看到有人贴图:同一条轴上的两路振动信号,普通幅值谱看着都差不多,在某个轴承故障特征频率附近却同时出现了一处明显的相干峰。下面跟了几条回复,有人问“相干峰到底代表什么”,有人说“…

2026/10/12 5:09:00 阅读更多 →
SpringBoot+Vue+MySQL旅游网站毕设项目全解析:从数据库设计到部署答辩

SpringBoot+Vue+MySQL旅游网站毕设项目全解析:从数据库设计到部署答辩

每年毕业季我都会收到大量和“旅游网站”相关的咨询,这套 SpringBootVueMySQL 的某北方城市特色旅游网站平台,属于完成度很高的一类毕设项目。它带了完整数据库脚本、论文文档和部署说明,代码结构比多数网上流传的“半成品”要规矩得多。这篇…

2026/10/12 5:09:00 阅读更多 →
微客AI助手答疑:AI客服的会话记录存在哪?留存位置与合规要点

微客AI助手答疑:AI客服的会话记录存在哪?留存位置与合规要点

给商家配微客AI助手的时候,被问过的最认真的一组问题来自一位做母婴用品的店主。她问的不是价格也不是功能,而是:客户的聊天记录存在哪?谁能看到?会不会被拿去做别的?说实话,这三个问题比大多数…

2026/10/12 5:08:00 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →