内网大文件互传系统实战:分片上传与断点续传设计详解
发布时间:2026/10/13 7:27:15 锦皓数字建站

手机和电脑在同一个局域网里互相传文件还要带聊天单文件最大能到50GB以上——这需求听起来不复杂但真做起来和你在外面用网盘发链接完全是两回事。我大概花了两个周末把一套内网文件互传和聊天的小工具从能跑调到真正敢传大文件中间踩了不少坑。这篇记录一下整体设计、核心实现和实测数据给同样需要在办公室、工位或者家里几台设备之间传大文件的人一个参考。先说明白一点这个工具本质上是“内网专用”不依赖外部网络数据也不经过第三方服务器。手机和电脑只要连着同一个路由器打开浏览器就能传不用装App不用扫码注册也没有单文件大小限制。50GB这个数字不是拍脑袋定的而是我在传输虚拟机镜像、视频素材和数据集时反复测试后的结论。真正决定能不能传大文件的不是域名快不快而是分片策略、断点续传和服务端内存控制这几件事做得对不对。1. 项目拆解50GB这个数字到底意味着什么1.1 需求从哪来先说清楚我为什么要做这个系统最初是办公室几个人要共享文件设计稿、安装包、录屏素材动辄几个GB甚至几十GB。用某个IM软件传经常遇到文件大小受限、过期被清理的问题用网盘中转上传下载都走公网几十GB的文件得挂一晚上。而且有些资料属于内部数据大家只希望在自己局域网里流转不想上传到任何外部服务器。所以需求很明确一个内网服务手机浏览器能传电脑浏览器能传还能顺手发个消息告诉大家“文件传好了”。后来我把这个工具整理成了一套独立方案给它起了个代号叫某跨平台内网互传系统。这套系统在中型办公室、家庭网络甚至临时组网环境下都跑过表现稳定。核心价值就一句话让大文件在局域网内以接近磁盘极限的速度流动同时把“传文件”和“说一句话”这两件事合到一个入口里。1.2 大文件场景下的三个硬约束50GB的文件不是普通文本处理它有三条硬约束每条都直接影响架构选型。第一是可靠性。50GB如果传到一半断掉从头再来是完全不能接受的。这就要求必须有断点续传能力服务端要清楚知道每个分片的接收状态客户端重启后能接着传。第二是速度。内网千兆环境下理论带宽约100MB/s一个50GB文件正常应该在8到10分钟内完成。如果协议设计不合理比如过度依赖小请求、串行传输、频繁校验时间会被拉长到半小时以上体验就很糟。第三是内存占用。传统做法是把整个文件读进内存再写入对50GB来说等于直接打爆服务端必须按块读写、边收边写。这三条约束基本把方案方向定死了基于HTTP的自建服务配合分片上传和断点续传而不是简单开一个静态文件服务器。1.3 应用场景与目标用户这套系统适合什么人用大概分三类。第一类是办公环境。比如某工作室需要给同事传10GB以上的工程文件顺便在群里喊一句“最新版本打包好了”。第二类是家庭网络。手机拍的一堆视频、电脑下载的蓝光资源、NAS备份都是动辄几十GB浏览器打开就能拖进去传比拔硬盘方便。第三类是弱网或无外网环境。像临时测试场地、仓库、实验室这种地方可能根本没有外部网络但只要设备在同一网段下这套工具照样能跑。使用门槛很低会打开浏览器就行。服务端部署也不需要很强的硬件我甚至在一台旧笔记本上跑过几十GB文件一样稳稳收下只是受限于磁盘速度。2. 方案选型为什么我没有用SMB和网盘2.1 常见方案的优缺点对比在动手写代码之前我把市面上的常见方案先过了一遍。不是为了造轮子而是想确认它们到底卡在哪里。下面这个表格是当时整理的思路方案优点缺点大文件体验网盘中转简单、跨网络限速、限大小、隐私风险几十GB文件基本传不动某IM软件传输操作熟悉大小限制、文件过期不好用FTP协议成熟、带宽能跑满移动端App体验差、断点续传依赖客户端传输尚可交互一般SMB共享Windows原生支持、速度快跨平台设置繁琐、手机端要装第三方播放器/文件管理器重文件没问题轻交互较差HTTP自建服务浏览器即开即用、可定制分片/续传/聊天需要自己开发控制权完全在自己手里对比下来SMB技术上是能传大文件的但它在手机上的体验不够统一。今天手机要访问SMB共享要么用系统文件App要么再装一个第三方管理器对办公室里不熟悉技术的同事来说成本偏高。FTP同理能传但聊天、分享、扫码这些体验完全做不出来。网盘更不用说五十个GB传到外网再下载回来时间成本和带宽成本都不可控。所以最终选择了HTTP自建服务这条路线。因为HTTP协议本身是浏览器原生支持手机和电脑打开网页就能传不需要装任何客户端。而且只有自建服务才能把分片策略、断点续传、任务状态、聊天模块统一设计做成一个真正贴合“内网互传”场景的工具。2.2 最终技术架构与模块划分整个系统服务端只有一个进程监听一个端口同时承担三类职责。一是静态页面服务。手机和电脑访问根路径时返回一个单页应用里面包含文件上传、文件列表、下载链接和聊天窗口。二是REST接口。负责文件上传、分片状态查询、任务创建、历史消息拉取。三是WebSocket通道。负责实时聊天消息的收发。数据存储上文件本身直接落盘到服务端的数据目录按上传任务ID组织文件夹避免同名文件互相覆盖。会话状态和消息记录存入嵌入式数据库方便重启后恢复断点续传状态。整体结构大概是这样的data/ uploads/ 任务ID1/ original_filename 任务ID2/ original_filename db/ meta.db logs/ server.log这个架构的好处是部署简单。只要把编译好的服务端二进制文件放到一台电脑上启动局域网内所有设备通过浏览器访问IP和端口就能用不需要额外配置数据库服务、消息队列或反向代理。2.3 聊天模块的协议选择聊天模块的选型有几个候选。最常见的是HTTP轮询但轮询的缺点很明显消息延迟高而且手机浏览器为了省电会限制后台定时器消息来了经常不刷新。另一种是WebSocket全双工通信服务端有消息可以立即推给客户端延迟基本在毫秒级很适合内网这种低延迟环境。最终聊天模块采用“HTTP拉历史消息 WebSocket收实时消息”的混合模式。进入页面时先通过HTTP接口拉取最近的消息列表建立WebSocket连接后新消息由服务端实时推送。这样兼顾了可靠性和实时性。聊天消息本身只保留文本、文件提醒和基础状态。每条消息有一个自增序号客户端根据序号判断是否漏收消息如果发现跳号就主动调用HTTP接口补拉历史消息。这个机制处理消息丢失非常管用后面会详细说。3. 核心细节解析大文件互传是怎么做到不崩的3.1 分片上传与断点续传的实现思路分片是解决大文件问题的最关键一步。通俗点说就是把一个50GB的文件切成几千个小块一块一块传。这样做有两个直接好处第一客户端不会一次性把整个文件载入内存第二传输中断后只需要重传没传完的分片而不是重新传整个文件。分片大小我实测过建议控制在4MB到16MB之间。分片太小比如1MB一个50GB文件会被切成五万多个分片每个分片都要发一次HTTP请求握手开销会占掉大量时间分片太大比如64MB一旦网络抖动导致某个分片失败重传代价就很大。我的经验是内网有线连接环境下用16MB普通Wi-Fi环境下用4MB或8MB更稳。客户端上传前先调用接口创建任务拿到一个全局唯一的任务ID。随后按分片编号逐个上传服务端每收到一个分片就落盘并在任务状态里标记该编号已完成。如果传输中断客户端重新打开页面时先查询任务状态拿到已上传分片的编号列表就从这个列表的下一个分片开始继续传。整个过程对用户来说就是点一下“继续”按钮。服务端数据模型可以很简单核心结构大概是这样的type UploadSession struct { TaskID string FileName string TotalSize int64 ChunkSize int64 Uploaded map[int]bool }接口上只需要三个核心端点创建会话、上传分片、查询状态。后面结合实际代码再说。3.2 进度计算、秒传与校验机制进度计算是所有上传页面都有的功能但要做好有几处细节。进度不能简单用“已完成请求数/总分片数”因为每个分片大小不一定完全相等最后一个分片通常更小。最准确的做法是记录累计已接收字节数然后除以文件总大小。秒传功能也值得做。如果用户拖入的文件名、大小以及文件头尾部分的哈希值与服务器上已有文件一致就直接返回“秒传完成”客户端瞬间跳转。我当时做了一版就是比对文件大小和抽样哈希没有对整个50GB文件做完整哈希因为完整计算一次MD5也要读一遍磁盘反而很慢。秒传只作为体验优化存在不参与数据校验。真正的传输校验要靠分片级别的完整性保证。每个分片上传时服务端写入后返回实际字节数客户端对账。传输结束时如果所有分片都已标记完成服务端就把分片路径记录更新为“上传完成”提供下载接口。对于内网使用场景分片大小校验已经足够不需要额外做端到端全文件哈希否则会把传大文件的时间拉长不少。3.3 手机与电脑自动发现与配对手动输入IP地址对手机用户不太友好所以这个系统做了自动发现。服务端启动时会通过局域网内的服务发现协议广播自己的地址和端口手机浏览器打开一个约定的名称就能直接看到可用服务。不过目前手机浏览器对自动发现协议的支持并不统一所以还保留了二维码方案。二维码方案很土但很好用。服务端在页面顶部生成一个二维码内容就是当前访问地址例如http://192.168.1.100:8080手机扫码后直接打开同一页面两边的文件列表和聊天窗口是实时同步的。这在办公室场景特别方便电脑页面上正在传一个进度条手机扫码进来也能看到这个任务并且能收到完成提醒消息。设备配对方面没有做复杂的账号体系以“同一服务端视为同一工作区”为准。所有连接到这个服务端的浏览器都会出现在同一聊天房间里文件任务也是共享的。这个设计特意保持简单因为在局域网内大家信任基础已经存在没必要加一套注册登录流程。3.4 聊天消息的收发与持久化聊天模块的实时通道用WebSocket实现关键点是心跳保活。手机浏览器切后台后WebSocket连接很容易被系统断开如果不做心跳再切回来时消息就收不到了。所以我给客户端加了一个定时器每30秒发送一次心跳服务端在60秒内没收到心跳就主动断开客户端检测到连接关闭后自动重连。消息结构大概包含这些字段msgId: 消息序号 session: 发送者会话标识 from: 发送者昵称 to: 目标空表示所有人 content: 消息内容 type: text / file / system ts: 服务端时间戳服务端收到消息后先写数据库再通过WebSocket广播给在线客户端。这样即使某个客户端中途掉线重新连接后也可以通过HTTP接口拉取历史消息把遗漏的补回来。聊天记录持久化到本地数据库还有一个好处重启服务端后聊天内容不丢文件断点续传状态也能快速恢复。4. 实操过程从零搭一套可用的内网互传系统4.1 环境准备与安装先说明一下服务端环境。我用了一台普通的Linux机器其实Windows和macOS也能跑只要支持目标语言即可。核心要求是网络稳定、磁盘够大。传50GB文件数据目录至少要留出两倍文件大小的空间因为分片写入时会产生临时文件合并后原分片还要保留一段时间作为备份。部署之前要做三件事。第一确认电脑IP是固定地址或者在路由器里设置DHCP保留否则重启后IP变了手机保存的地址就访问不了。第二确认端口不被占用和拦截。默认端口建议用8080如果和本机其他服务冲突就换一个高位端口。第三如果路由器开了“AP隔离”功能手机和电脑会处于隔离状态互相访问不了需要先关掉这个功能。安装过程非常简单。把编译好的服务端程序放到一个目录比如/opt/lanshare设置好数据目录权限然后用命令行启动./lanshare -addr 0.0.0.0:8080 -data ./data启动后终端会打印访问地址和内网广播状态。此时在电脑浏览器打开这个地址顺手用手机扫码就能看到同一个界面。4.2 服务端核心接口与配置服务端接口在设计时尽量保持精简。核心表如下接口方法说明/api/sessionPOST创建上传任务传入文件名、总大小、分片大小返回任务ID/api/chunkPOST上传单个分片携带任务ID、分片编号和文件内容/api/statusGET查询任务状态返回已上传分片列表/api/filesGET获取已完成文件列表用于下载/api/downloadGET按任务ID下载文件/api/messagesGET拉取最近聊天消息/ws/chatWebSocket实时聊天的连接通道创建任务时服务端保存文件原始名称和总大小但落盘文件名改为任务ID加扩展名避免特殊字符和路径穿越问题。上传分片时服务端根据任务ID找到对应目录把分片内容顺序写入分片文件写入成功后更新状态表。为了限制并发和内存占用我还加了两个简单配置项一是最大并发分片上传数默认8个二是每个分片的最大内存缓冲默认32MB。这样一来即使同时有好多个人在上传几十GB文件服务端内存也不会被拖垮。4.3 客户端操作流程与界面要点客户端的核心页面就是“上传区 文件列表 聊天窗口”。上传区的实现需要注意一个浏览器特性用户选中一个50GB文件后浏览器可以拿到File对象但绝对不能直接执行readAsArrayBuffer把整个文件读进内存。正确做法是使用File.slice方法切分文件然后逐个把分片发送到服务端。以网页端为例核心逻辑可以写成这样async function uploadFile(file, taskId) { const CHUNK_SIZE 4 * 1024 * 1024; let offset 0; while (offset file.size) { const chunk file.slice(offset, offset CHUNK_SIZE); const form new FormData(); form.append(taskId, taskId); form.append(index, Math.floor(offset / CHUNK_SIZE)); form.append(chunk, chunk); await fetch(/api/chunk, { method: POST, body: form }); offset CHUNK_SIZE; updateProgress(offset / file.size); } }这只是一个示意真实实现里还需要处理重试、超时和任务状态查询。但是小程序和大文件上传基本遵循这个思路绝对不要一次性读入内存而是按分片推进。界面部分进度条、速度为必显项另外一定要显示“剩余时间”。因为大文件传到后面用户盯着百分比很容易焦虑有剩余时间会安心很多。聊天窗口不需要做得太重只要能显示消息、发送文本、显示文件完成提醒就够了。文件完成提醒由系统自动发送比如“某台设备上传了 xxx共 45.2GB”。4.4 性能调优与实测数据实测环境是千兆有线局域网服务端使用普通机械硬盘手机通过Wi-Fi 5连接。单线程上传一个10GB文件实测平均速度约98MB/s基本接近千兆带宽的上限。换成NVMe固态硬盘后速度能稳定到110MB/s左右说明瓶颈已经从网络转移到了磁盘。如果速度跑不满优先排查三个位置。第一是路由器很多家用路由器的Wi-Fi带宽是共享的手机贴着路由器测一下就能看出差别第二是网线劣质网线或者水晶头接触不良会直接降速第三是磁盘机械硬盘在写大文件时本身能到150MB/s以上但如果有多个任务并发写入磁头寻道会拖慢整体速度。服务端侧我做了两个调优。一是分片文件先写内存缓冲区达到阈值后再一次性flush到磁盘减少小碎片写入次数。二是对下载操作启用按需读取客户端通过HTTP Range请求下载大文件时服务端不读入全部文件而是从磁盘流式读取指定范围这样下载50GB文件时服务端内存占用稳定在几十MB。5. 常见问题与排查技巧实录5.1 传大文件时服务端内存爆掉这是刚开始最常遇到的问题。最初一版设计里客户端把一个分片上传上来服务端就整片读入内存再写盘分片设得又大几个任务并发之后内存直接吃满。解决方案就是改成分片流转写。服务端接收HTTP请求体时使用类似“边接收边写入临时文件”的方式不在内存中保留完整分片。同时把分片默认值调小到8MB并限制并发数。改完之后连续传多个50GB文件服务端内存占用也基本保持平稳。如果你自己实现记得测试时用top或任务管理器盯一下服务端内存曲线一旦出现持续增长多半是某个接口没有释放缓冲。5.2 手机传了一半后台被杀手机浏览器在锁屏或者切到后台后页面脚本会被系统挂起上传进度会停住。这个不是代码问题而是移动浏览器的资源管理机制。我用两个手段缓解。一是尽可能使用“托管上传”方式。用户发起大文件上传后页面需保持前台同时提示用户不要锁屏。我测试下来iPhone和Android手机在保持屏幕常亮、浏览器前台运行时可以连续上传一个多小时不掉线。二是依赖断点续传兜底。如果中途断了锁屏前已经上传的分片都留在服务端重新打开页面后选择同一个文件程序会查询已上传分片列表只补传剩余部分。最坏情况下也要让用户知道不需要从头开始。后续我还计划做一个手机端的原生上传页面用浏览器的后台能力来规避前台限制但这属于增强功能基础网页版已经能完成核心任务。5.3 聊天消息乱序或丢失WebSocket本身是顺序传输的但客户端网络切换时可能出现短暂重连消息序号跳号。早期版本没有处理跳号用户反馈偶尔会漏掉消息。后来加了一个机制每条消息带自增序号客户端收到消息后检查序号是否连贯如果发现跳号就调用/api/messages接口按序号范围补拉。消息乱序更多出现在“重连后的历史消息合并”阶段。新拉取的历史消息和实时消息混合渲染时可能先显示了新消息后插入旧消息。解决方法是在客户端维护一个以序号为键的消息数组渲染时统一排序。这里也提醒一下不要在渲染时把消息插到数组头部直接按序号插入合适位置再整体重绘逻辑会简单得多。5.4 内网传输速度跑不满怎么办速度跑不满是最容易被错误归因的问题。实际排查里七个案例有五个是无线网络问题。最常见的是手机连接的是2.4GHz频段旁边干扰严重实际带宽只有几十Mbps。先把手机切到5GHz频段测试能拉开明显差距。有线环境下如果还是跑不满检查服务端是否开了限速配置。我当时为了不占满办公室带宽默认写了每任务限速40MB/s测试时忘了关还以为是协议问题排查了半天。最后把限速改成可选配置默认不限速问题才算解决。另外如果传输的是大量小文件性能瓶颈就在随机IO和网络关系不大这种情况建议压缩后再传。5.5 安全与隔离的几点提醒内网工具不代表可以不考虑安全。我当时做的时候加了一个访问令牌客户端必须输入服务端启动时生成的随机密码才能进入页面防止同一局域网里的陌生设备扫到端口直接上传垃圾文件。这个密码在服务端启动参数里配置也可以通过配置文件修改。另外文件下载接口做了路径约束。所有下载请求都先映射到任务ID再由服务端解析对应的文件路径禁止客户端直接传任意路径参数。这样能有效防止路径穿越类的安全问题。上传文件统一改名保存不直接使用原始文件名拼路径也是基本操作。聊天内容虽然只在局域网流转但正常企业环境里还是建议避免发送敏感信息。如果特别在意这点可以考虑在聊天模块上叠加一个简单的消息加密功能比如端到端加密这是后续扩展的方向基础的文本记录还是以明文方式存储。这个工具做到现在我最深的体会是“先跑通再优化优化永远从内存和断点开始”。分片上传和断点续传听上去是老技术但真要把50GB文件稳定传完里面每一环都需要实际压测。你先拿一个5GB文件把流程跑通再上30GB、50GB遇到问题逐项排查很快就能摸清整个系统的脾气。最后再分享一个小技巧如果长期使用给服务端加一个每日定时任务自动清理超过保留期限的旧文件这样数据目录不会因为频繁传大文件而失控。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。