局域网文件传输工具Davor:从设备发现到WebSocket传输的完整实践 “Davor”这名字说来挺随意。我有个朋友做嵌入式出身帮我调过几次网络问题每次都是远程开着终端敲几行命令就完事省了我大把折腾时间。有次喝酒我说以后我做个小工具就起你名字。后来真做出来了一个跑在局域网里的跨设备传输工具名字就叫 Davor。这个东西不新甚至有点朴素——它就是让你在同一网络下不用数据线、不用登网盘、不用经过任何中转服务器直接把文件从一个设备传到另一个设备的工具。但就是这样一个朴素工具从设计到落地中间踩过的坑、做过的取舍我觉得值得拿出来聊聊。如果你也遇到过“手机照片导电脑找不到线”“给同事传个几百兆的包传到一半就断”“两台电脑拷文件只能靠U盘当搬运工”这类场景那你应该接着往下看。1. 设计与定位一个“只做一件事”的局域网工具1.1 先说清楚 Davor 到底是什么Davor 本质上由一个常驻后台的服务程序加一个浏览器网页组成。服务程序跑在其中一台设备上监听某个端口同一局域网里的其他设备无论是手机、平板还是另一台电脑只要打开浏览器访问那个地址就能看到网页界面然后直接拖拽文件进去完成发送或下载。整个链路里没有任何云端参与。文件从源设备经过局域网网络到达目标设备不占用公网带宽也不在第三方服务器上留下副本。这一点和很多人习惯的“传文件传网盘”的思路完全不同。它的适用场景很明确人在同一间办公室、同一个家、同一个实验室设备之间物理距离很近网络在同一网段但你就是找不到一根合适的线或者不想为了传个文件去登录云盘、连蓝牙、开一堆权限。选择这个定位是我有意把范围缩得很小。市面上成熟的传输方案太多了但它们要么依赖账号体系要么依赖公网中转要么配置复杂到普通人根本不会去碰。Davor 的方向是零安装、零账号、零学习成本。接收方不需要装任何客户端一个浏览器就够。发送方也只需要在网页上拖文件剩下的交给程序处理。1.2 常见的文件传输方式为什么都有点别扭我把日常传输场景列过一张对比表越看越觉得有一个明显的空档方式优点痛点数据线直连速度快、稳定要找对线、驱动、接口手机还得解开屏幕锁确认授权即时通讯软件传文件方便、有记录文件大小限制严格传输质量受服务器中转影响隐私上等于经过第三方网盘上传分享可跨地域访问要上传再下载几GB的文件耗时长还得处理链接有效期蓝牙/Wi-Fi直连短距离可用配对麻烦、速度波动大、历史文件管理弱自建FTP/SMB共享功能强、通用搭建和配置门槛高普通用户很难自己独立搞定这些方案都能用但在“同一局域网、临时传一下文件”这个场景下都有点杀鸡用牛刀或者别扭的感觉。Davor 想补上的就是这块空隙它不需要你理解什么是共享、什么是目录权限、什么是FTP被动模式。打开网页拖文件完事。1.3 功能边界我主动砍掉了什么做这个项目时我在功能选择上非常克制。很多东西不是不会做而是刻意不做。排在最前面的三个“不”不做公网穿透、不做多用户权限体系、不做长期存储。不做公网穿透意味着 Davor 永远不会在互联网上被直接访问到只能在局域网里用。这同时是安全设计的一部分——攻击面被限制在一个可信的物理网络内。不做多用户权限是因为这个工具的服务对象就一两个熟人一个浏览器会话对应一个传输入口搞复杂的登录注册只会增加使用阻力。不做长期存储是因为它的定位是“传完即走”文件直接落盘到接收方指定的目录不走中转池不搞云端缓存。被做减法砍掉的功能还包括跨网段自动发现、断点续传的增强策略、多设备同时管理的面板。这些不是没有价值而是第一版不值得为它们牺牲稳定性和简洁性。一个工具如果能让你在十秒内完成一次文件传输那它就是成功的如果每次使用前还要理解“会话”“令牌”“存储配额”这些词那它再强大也违背了初衷。2. 技术链路设备发现、传输协议和文件校验怎么配合2.1 设备发现让两台设备在局域网里“认出”彼此Davor 启动后最核心的一个问题不是“怎么传”而是“怎么让对方找到自己”。在局域网环境里最蠢也最可靠的办法是服务程序启动时打印出当前设备的局域网IP和端口用户把这串地址手动输到另一台设备的浏览器里。我保留了这种手动模式作为兜底但日常用起来毕竟不够优雅所以又加了一条自动发现的路mDNS也就是组播DNS。mDNS 的原理可以理解成“设备在小范围里主动自我介绍”。服务端启动时会向局域网内的组播地址发送一条通告内容是“我叫_davor._tcp.local我在这个IP的某个端口提供服务”。同一网络里其他支持 mDNS 的设备听到这条通告后就能在系统网络服务列表里看到一个名为 Davor 的条目点击即可访问。选择 mDNS 而不是普通的UDP广播是因为组播的报文能跨不少三层网络设备传输而广播在多数网段中间设备上会被直接丢弃。我最初用的是 UDP 广播结果在两个办公室组网的环境里无论如何都发现不了对方查了一圈才发现广播域被中间路由隔开了。换到 mDNS 后问题立即消失。这里还要注意一个细节mDNS 通告是周期性发送的默认间隔很短但收件端的缓存时间TTL直接影响发现速度。我把通告间隔设成5秒一次TTL设成120秒这样一台新设备加入网络后最快5秒内就能发现服务同时不会因为频繁刷新给网络带来多余负担。2.2 传输通道为什么最终选了 WebSocket文件传输的方式有好几条路可以走每条路的代价完全不同。第一种是浏览器直接发起 HTTP 上传后端接收文件流再给下载方提供一个链接。这是最简单的方案但问题出在“下载”这一侧。如果接收方也想通过浏览器操作它就要不断刷新页面来确认是否有新文件或者轮询接口体验和实时性都一般。另一种是双方都通过 HTTP 接口轮询状态编程简单但延迟高、请求多。第三种是建立一条长连接实时双向收发数据——这就是 WebSocket。Davor 最终选择 WebSocket 作为传输通道。原因有几个一是浏览器原生支持几乎任何现代设备打开网页就能用不用装驱动、不用装插件二是双工通信让“发送端传文件、接收端收文件”的操作可以在同一页面里实时同步进度三是 WebSocket 本身就基于 TCP在局域网这种低丢包、高带宽的环境里吞吐量完全够用。我做过一轮实测在千兆局域网环境、网线直连的条件下通过 WebSocket 传输一个约2GB的文件实际平均速度能达到80MB/s以上接近磁盘写入瓶颈。对于绝大多数临时传输需求这个成绩已经绰绰有余。用 Node.js 的 WebSocket 库做服务端内存压力可控部署也简单。2.3 文件完整性分块、校验与断点续传的取舍传文件最怕什么不是慢而是传完之后文件损坏了你完全没察觉拿到手才发现打不开。对于文本文件哪怕损坏几个字节可能都注意不到但换成一个压缩包或数据库备份损失就大了。所以 Davor 在传输层加入了分块校验机制。实现思路是文件在发送端被切成固定大小的块每块计算一个校验值连同块序号一起通过 WebSocket 传给接收端。接收端每收到一块马上计算本地校验值并同收到的值比对一致才落盘不一致就请求重发这一块。块大小我最后定在512KB这是综合网络环境、内存占用和校验粒度后权衡出来的值。块太大出错重传的代价高块太小校验和序号的开销占比过高影响整体速度。顺带解释一下为什么校验用 SHA-256 而不是老的 MD5。在局域网这种高速环境下计算开销和传输速度相比并不明显而 SHA-256 在安全性上至少碰撞概率低到可以忽略。MD5 在几十年前的设计里没有考虑恶意碰撞问题虽然日常传文件撞上概率极小但不值得为了省一点CPU时间冒风险。至于断点续传我第一版没做完整的服务端断点支持只做了接收端断电重连之后对未完成的临时文件自动清理不留下半截垃圾。完整的断点续传需要维护每个文件块的接收状态表服务端要感知每个客户端拿到了哪些块这会让状态管理复杂一个量级。评估下来局域网的特点恰恰是连接相对稳定断点续传属于“低频高成本”功能缓一步更划算。2.4 安全模型局域网不是绝对安全的避风港Davor 默认没有登录密码任何能访问到服务端口的人都可以看到当前网页。这在很多设计者眼里是致命缺陷但我刻意保留了这个默认行为因为密码本身就是使用阻力。一台家庭路由器里的局域网能进到这个网络的人一般来说就是你的家人、客人信任模型是成立的。但完全不设防也不行。我在上传和下载接口上加入了一个一次性令牌token。这个令牌在服务启动时随机生成并且在网页地址里体现。简单说访问链接本身就是一个“密码”只不过这个密码被藏在URL里不会明文散落。第一次访问生成页面时程序会把令牌绑定到WebSocket连接的握手阶段后续的数据传输都会校验这个令牌是否匹配。如果你的使用场景是公司内部网络面对的是几十上百号人不完全可信的环境我强烈建议打开 HTTPS 选项。Davor 支持通过环境变量传入证书和私钥路径启用后整个传输链路加密令牌也不会以明文形式在局域网里被其他设备嗅探到。这套安全模型不算复杂但足够覆盖它面对的典型威胁防搜索扫描、防窃听、防未授权访问而不是去对抗国家级攻击者。3. 从零实操下载、配置、接入全流程3.1 环境准备和快速启动Davor 的服务端实现选的是 Node.js 和 Golang 混合的方案——核心文件服务用 Go 编译成单一二进制文件网页前端和 WebSocket 网关则用 Node.js 管理。最终交付物非常简单一个可执行文件加一个静态网页目录。如果你不想自己编译可以直接到发布页下载对应平台的压缩包解压后运行。以 Linux 环境为例解压后目录结构大致看起来像这样davor/ davor-server # 主服务程序 ui/ index.html # 网页入口 app.js # 前端逻辑 style.css # 界面样式启动命令直接就是./davor-server --port 8310 --storage ./uploads--port指定监听端口默认8310之所以选这个端口而不是常见的80或8080是为了避免端口冲突也降低被扫描到的概率。--storage指定接收文件的存放目录程序会在目录不存在时自动创建。启动后终端会打印两类信息一类是访问地址比如http://192.168.1.12:8310另一类是设备发现专用服务名_davor._tcp.local。如果你的电脑上有多个网络接口比如同时连着 Wi-Fi 和网线服务启动时只会监听其中指定的一个地址。这时候最好用--bind参数显式指定 IP否则可能出现手机能访问、电脑反而访问不了的诡异情况。3.2 关键配置项逐项说明配置项不多但每一项踩过坑之后都变得有分量。我整理成一张表方便按需调整配置项默认值作用说明注意事项--port8310服务监听端口避免与已有服务冲突开放防火墙里对应TCP入站端口--bind0.0.0.0绑定到哪个网络地址多网卡时务必指定具体IP否则可能绑定到错误的网卡--storage./uploads文件落盘目录需要足够的磁盘空间建议单独分区--token随机生成访问令牌指定固定令牌便于调试生产环境保持随机--max-size0不限单文件大小上限单位MB防御超大文件拖垮内存--tls-cert/--tls-key空HTTPS证书路径开启后强制走加密通道这里最容易被忽略的是--max-size。如果不设限制一旦有人往页面里拖一个几十GB的虚拟机镜像服务端会把整个文件逐步读进内存做缓存同时写盘内存占用会瞬间暴涨最终导致其他设备访问卡死。虽然实际场景里出现这种超大型文件的概率不高但设一个合理上限比如4096MB等于给服务上了保险。3.3 核心交互流程与接口设计Davor 的网页界面遵循极简原则打开页面就是一个大拖拽区下面是一个待传输文件列表。拖入文件后自动开始上传进度条实时显示。发送完成后接收方设备打开相同地址界面会列出当前会话中已到达的文件点击即可下载。接口层面主要就三个。第一个是建立 WebSocket 连接const socket new WebSocket(ws://${location.host}/ws?token${TOKEN});第二个是发送文件元数据格式为JSON包含文件名、大小、块数{ type: file-start, name: demo.tar.gz, size: 209715200, chunkCount: 400 }第三个是发送二进制块也就是文件内容本身。我选择用二进制帧传输在 WebSocket 协议里数据帧直接传ArrayBuffer比用 Base64 编码字符串再传要节省约33%的体积速度优势在百兆网络下尤其明显。接收端按块序号写盘全部块到位并校验通过后文件才算完成。如果你习惯用命令行脚本调这个服务也可以用 curl 配合 HTTP 接口做纯上传无需浏览器curl -X POST --data-binary file.tar.gz \ -H Content-Type: application/octet-stream \ -H Authorization: Bearer TOKEN \ http://192.168.1.12:8310/api/upload?namefile.tar.gz这种设计让 Davor 不只服务于“人用网页拖文件”这一种场景也适合在服务器之间、或者脚本里紧急传递一批配置文件。我实际就在一台内网机器上用它传过日志包命令行调用比写一套 scp 脚本还省事。3.4 跨平台接入手机、平板、电视盒都能用跨平台这个词在 Davor 这里不是靠适配各种操作系统实现的它靠的是“浏览器就是客户端”这个朴素事实。iOS 上的 Safari、安卓上的 Chrome、笔记本上的 Firefox、智能电视自带的浏览器只要能打开网页、能访问到局域网内那个地址就能收发文件。对于手机这类没有方便文件拖拽入口的设备我在网页上做了一个“选择文件”按钮点击后调起系统文件选择器。第一次使用时建议把页面添加到主屏幕本质上就是一个不用安装的轻量应用。我在实际使用中还发现一个很实用的组合手机扫码连接。服务启动时终端会打印一个二维码图案另一台设备用默认相机扫码直接跳转到访问地址省去手动输入IP的麻烦。不过在移动端有一个细节要提醒手机浏览器在锁屏或切换到后台时WebSocket 连接很可能会被系统挂起导致上传进度停止。这是移动操作系统的功率优化机制不是 Davor 自身的问题。解决方法是传输过程中保持屏幕常亮或者把浏览器切换到前台挂在那边另外别指望锁屏后继续上传几十GB的文件这不现实。4. 踩坑记录实操中遇到的那些“看似玄学”的问题4.1 设备之间互相发现不了先从这三处查起局域网环境下最让人抓狂的问题是明明两台设备都连着同一个 Wi-Fi但手机就是打不开电脑上 Davor 的页面或者 mDNS 列表里看不到服务。这一类问题有超过80%的概率出在下面三个地方。第一是防火墙。很多操作系统默认禁止外部设备访问本机的非标准端口。Davor 启动后监听的8310端口如果没在防火墙入站规则里放行结果是电脑自己访问http://localhost:8310非常顺畅换上局域网IP访问就超时。排查方法很简单在另一台设备上执行nc -vz IP 8310如果端口是关闭状态基本就是防火墙拦截。第二是无线接入点的“AP隔离”功能。不少路由器默认或用户突然开启了AP隔离这会禁止无线客户端之间互相访问所有无线设备只能和路由器通信。症状是手机能上网但访问局域网内其他设备全部失败。解法是登录管理后台关掉这个选项。第三是多网卡环境下的绑定错位。前面提过同一个人电脑上有线网卡和无线网卡同时在线时服务可能绑定到了未使用的那个地址上。这种问题最隐蔽因为程序打印的访问地址看起来完全正常实际却指向了一个无法被其他设备访问的网段。用--bind显式指定地址后问题马上消失。4.2 传输速度千差万别瓶颈不在网络而在磁盘和内存有人跟我反馈Davor 在同一台路由器下传输速度只有我自己测速的三分之一。一开始我怀疑是Wi-Fi信号问题后来远程排查发现问题出在接收端电脑的磁盘上——那是一块老的机械硬盘顺序写入性能本身就跑不满千兆。文件的传输速度并不是取发送端和接收端网络带宽的简单关系而是整条链路中最弱一环的速度。发送端从磁盘读文件经过协议栈封装通过网络传输接收端解包后写盘。任何一个环节慢了都会成为瓶颈。尤其要注意如果接收端把文件写入到一块速度很垃圾的U盘上那再怎么优化网络也没用U盘的写入速度就是天花板。内存也不能忽视。Davor 默认会在内存里维护一个滑动窗口缓存已经发送但还没收到确认的若干数据块。窗口设置过大遇到网络轻微波动时积压的数据很多影响重传效率窗口过小则会因为频繁等待确认而拉低吞吐。我在实现里把窗口调成 1024 个块配合512KB的单块大小大约是512MB的飞行中数据在千兆局域网里取了一个速度与可靠性的平衡点。4.3 中文文件名、空格和特殊字符引发的问题文件名的坑往往是新手最想不到的。我头几次测试时传的是英文名文件一切正常直到有一天传了个“项目预算表(最终版).xlsx”这样的文件接收端落盘后文件名变成了乱码或者干脆报错。根因有两个一是 HTTP 和 WebSocket 协议栈在传输路径上有不止一层的 URL 编码和解码中文经过多轮编码后如果某一层用错了字符集就会出现乱码二是文件系统对字符的处理规则不同——Windows 不允许文件名包含冒号等字符Linux 却照单全收。解决方法是统一在发送端用encodeURIComponent做编码传输接收端落盘时用decodeURIComponent还原同时把文件名里的非法字符提前替换成下划线。传含空格的文件夹时还要保证前端显式处理了引号。这类问题排查起来不算难但每次出现都有一种“怎么又是你”的熟悉感所以在实现里干脆在文件元数据下发前做一次全量字符清洗不合法字符统一转义宁可名字里多几个下划线也不要文件无法落盘。4.4 WebSocket 意外断连和重连的应对局域网虽然比公网稳定但 Wi-Fi 信号漂移、系统休眠、网络切换都会让长连接意外断开。Davor 最头疼的一个现场是文件传到一半接收端设备锁屏WebSocket 断开发送端还在傻傻地推数据对面已经没人接收。处理思路分为两层。发送端一侧每发送一个数据块就开启一个短计时器如果在2秒内没有收到接收端的确认帧就认为连接可能已经过期主动关闭连接并通知界面显示“连接中断请检查接收端状态”。接收端一侧断线后立即停止写盘删除当前不完整的临时文件避免留下残卷占用磁盘。其实最稳妥的做法是建立一个可恢复会话。但因为 Davor 的设计原则是“轻”我没有实现完整的会话恢复而是选择把断连的代价控制到最小。对于绝大多数一次传输几个文件以内的使用场景重新传一次的成本远低于维护会话状态表的复杂度。这个决策不完美但足够实用。5. 一点使用体会当工具被真正用起来以后Davor 从随手起名到能稳定运行中间改了很多版。如今我自己的使用模式已经固定电脑上常驻一个终端窗口跑它手机如果需要给同事传文件直接扫码打开网页拖文件不用再折腾数据线和各种软件。家里那台老笔记本也装了一份偶尔把相机里的照片导过去存着或者把下载好的电影发到客厅的电视盒子上播放全程不用插拔存储卡。要说这个项目有什么值得回味的经验我的体会是做一个工具最先要清楚的不是“它能做多少”而是“它在哪个场景下能让人毫不犹豫地用它”。Davor 的成功之处不在于它有多强的功能而在于它把“同一局域网内传文件”这个动作压缩到了十几秒以内并且没有任何前置门槛。任何多出来的步骤都是在给用户增加放弃的理由。另外还有一个细节上的小建议如果你也在写类似的自用工具一定要把启动时的日志打印得足够友好。Davor 启动时会把访问地址、令牌、二维码全部打印在终端里这看起来只是一个小小的贴心设计实际使用中却省了很多事。几乎每次换个新设备、换了个网络环境我都要靠这些信息快速找到入口。工具好不好用很多时候就藏在这些不起眼的边角里。