LocalSend 开源跨平台局域网文件共享:工作原理、网络配置与源码级实战指南
【免费下载链接】localsendAn open-source cross-platform alternative to AirDrop项目地址: https://gitcode.com/GitHub_Trending/lo/localsend
LocalSend 是一款免费开源的跨平台应用,允许你在局域网内与周边设备安全地共享文件和消息,全程无需互联网连接。本文基于项目官方文档(捷克语版 README 及其对应的 README.md),结合仓库中 Rust 核心协议实现(packages/core)与 Flutter 应用层源码(app/lib),系统讲解 LocalSend 的分发渠道、兼容性与网络配置、可移植模式、--hidden参数、自签名 TLS 证书与设备指纹机制,以及从源码编译运行的完整流程。读完后,你将能够独立完成 LocalSend 的部署、防火墙放行、便携化使用,并理解其"无服务器、端到端加密"架构在源码中的落地方式。
项目定位:不依赖任何第三方服务器的局域网传输
LocalSend 是一款多平台应用,通过 REST API 与 HTTPS 加密实现设备间的安全通信。与其他依赖外部中转服务器来中转消息的传输应用不同,LocalSend 不要求互联网连接,也不涉及任何第三方服务器——所有通信都直接发生在局域网(LAN)内的设备之间。这使其成为本地文件投递的快速、可靠方案:
- 无互联网依赖:数据不出局域网,适合内网隔离环境、办公场景与隐私敏感场景;
- 多平台对等:Android、iOS、macOS、Windows、Linux、Fire TV OS 之间可以互相发现与互传;
- 开源可审计:协议核心用 Rust 实现(packages/core/src),应用层用 Flutter 实现(app/lib),完整仓库开源可查。
下载渠道
由于应用没有内置自动更新机制,官方建议通过应用商店或包管理器获取版本,以便后续更新。各平台的主要分发渠道如下:
| Windows | macOS | Linux | Android | iOS | Fire OS |
|---|---|---|---|---|---|
| Microsoft Store | Mac App Store | Flathub | Google Play Store | App Store | Amazon Appstore |
| Winget | Homebrew Cask | Nixpkgs | F-Droid | ||
| Chocolatey | DMG 安装器(GitHub Releases) | Snap | APK(GitHub Releases) | ||
| EXE 安装器(GitHub Releases) | AUR | ||||
| Portable ZIP(GitHub Releases) | DEB(GitHub Releases) | ||||
| AppImage(GitHub Releases) |
更完整的分发渠道说明见 CONTRIBUTING.md。
兼容性
| 平台 | 最低版本 | 备注 |
|---|---|---|
| Android | 5.0 | — |
| iOS | 12.0 | — |
| macOS | 11 Big Sur | 更低版本可用 OpenCore Legacy Patcher 2.0.2 安装 |
| Windows | 10 | 最后支持 Windows 7 的版本为 v1.15.4,未来可能对 Win7 做版本回移 |
| Linux | 无强制要求 | — |
网络配置:防火墙与路由器的两个关键点
绝大多数情况下 LocalSend 安装后即可直接使用。如果收发消息或文件出现问题,通常需要检查两处网络设置。
1. 防火墙放行 53317 端口
| 流量方向 | 协议 | 端口 | 操作 |
|---|---|---|---|
| 入站 | TCP、UDP | 53317 | 允许 |
| 出站 | TCP、UDP | 任意 | 允许 |
这个端口不是文档拍脑袋给的数字——它就是协议默认端口。在 Rust 核心库中,UDP 组播发现使用的默认端口与 HTTP 服务器默认端口是同一个常量:
// packages/core/src/multicast/mod.rs(第 38-39 行) /// The default multicast port, identical to the default HTTP server port. pub const DEFAULT_PORT: u16 = 53317;也就是说,53317 上同时承载两类流量:TCP 的 REST/HTTPS 数据传输,以及 UDP 的设备广播发现。CLI 端的配置文件默认注释也印证了这一点(cli/src/storage/config.rs 中#port = 53317与const DEFAULT_PORT: u16 = 53317)。
2. 路由器禁用 AP 隔离(客户端隔离)
请确认路由器上已关闭"访问点隔离"(AP isolation / client isolation)。该功能默认通常是关闭的,但部分路由器——尤其是访客网络(Guest Network)——会将其开启,一旦开启,设备之间的通信会被直接阻断,LocalSend 便无法互相发现。
高级用法:可移植模式与托盘隐藏启动
可移植模式(Portable Mode)
自 v1.13.0 起,桌面版支持"可移植模式":在与可执行文件同一目录下放一个名为settings.json的文件(内容可以为空),应用就会把配置存储到这个文件里,而不是默认的操作系统级配置目录。
这一行为在应用层有明确的实现:app/lib/util/shared_preferences/shared_preferences_portable.dart 中的SharedPreferencesPortable是一个自定义的 SharedPreferences 存储实现,它通过buildSettingsPath计算可执行文件所在目录下的settings.json路径,当Platform.resolvedExecutable无法解析时(例如某些 RAM 虚拟磁盘场景)会回退到当前工作目录:
// app/lib/util/shared_preferences/shared_preferences_portable.dart String buildSettingsPath({ required String? executablePath, required String Function() fallbackDirectory, }) { final directory = executablePath == null ? fallbackDirectory() : File(executablePath).parent.path; return path.join(directory, 'settings.json'); }该逻辑有单元测试覆盖(app/test/unit/util/shared_preferences/shared_preferences_portable_test.dart,断言便携模式会把settings.json放在可执行文件旁)。相比之下,非便携模式下配置位于系统数据目录,如 app/lib/provider/persistence_provider.dart 中 Windows 路径%APPDATA%\LocalSend\settings.json。可移植模式的意义在于:整个应用目录拷走即带走全部配置,非常适合 U 盘便携使用或容器化部署。
托盘隐藏启动(--hidden)
自 v1.15.0 起,想让应用启动时最小化到托盘/菜单栏(不显示主窗口),请使用--hidden启动参数,例如:
localsend_app.exe --hidden在 v1.14.0 及更早版本中,只有当系统设置了autostart启动项且"启动时隐藏"开关打开时,应用才会隐藏启动;新版本统一改为直接监听--hidden参数,行为更稳定、更可预期。
源码中这个参数以常量startHiddenFlag = '--hidden'定义在 app/lib/util/native/autostart_helper.dart,并贯穿三个平台的自启动注册逻辑:
| 平台 | 自启动实现 | --hidden的落点 |
|---|---|---|
| Linux | 写入~/.config/autostart/<app>.desktop桌面项 | 追加到Exec=行 |
| macOS | 通过 Launch at Login API | setLaunchAtLoginMinimized(startHidden) |
| Windows | 写入注册表HKCU\Software\Microsoft\Windows\CurrentVersion\Run的LocalSend值 | 追加到注册表启动命令行 |
因此"开机自启 + 隐藏启动"的组合,本质就是自启动项的命令行里带上--hidden。
工作原理:REST API + 运行期自签名 TLS + 设备指纹
LocalSend 使用一套安全的通信协议,让设备之间通过 REST API 直接对话。所有数据都通过 HTTPS 传输,TLS/SSL 证书在每台设备运行期就地生成——没有 CA、没有证书分发、没有服务器参与。
自签名证书与设备身份
协议核心库 packages/core/src/crypto/cert.rs 中的generate_self_signed()揭示了完整的身份模型:
- 生成RSA-2048密钥对(与早期 Flutter 应用用 Dart 生成的证书保持兼容);
- 自签名证书的 CommonName 为
LocalSend User,不带 SAN——因为对等设备完全靠证书指纹相互识别,名字不承载任何信息; - 有效期取 rcgen 默认值(约 1975 年至 4096 年),证书实际上永远不会过期,因此无需因时间原因轮换;
- 序列号由公钥哈希派生(rcgen 默认行为)。
返回结构SelfSignedCert同时保存 PEM 编码的私钥(PKCS#8)、公钥(SPKI)、证书,以及证书的SHA-256 指纹(DER 字节序列的大写十六进制):
// packages/core/src/crypto/cert.rs pub fn fingerprint_from_cert_der(cert: &[u8]) -> String { crate::crypto::hash::sha256(cert) .iter() .map(|byte| format!("{byte:02X}")) .collect() }设备指纹(Fingerprint)是 LocalSend 的安全锚点:接收方看到发件设备后,可以核对并记住其指纹(在 UI 中还会把指纹哈希后渲染成 16 个图标,便于肉眼比读,见 app/lib/util/fingerprint_alphabet.dart)。收藏设备(Favorites)时同样是按指纹精确匹配(app/lib/util/favorites.dart)。
验证侧同样严格:verify_cert_from_cert会依次检查时间有效期、公钥是否匹配(若提供)、以及签名本身,任一失败都会抛出可区分的错误。测试用例 packages/core/src/crypto/cert.rs 明确覆盖了"签名被篡改""公钥不匹配""证书过期"三类恶意/失效场景,确保防御逻辑真实生效。
UDP 组播发现:如何"看到"附近设备
发现阶段走 UDP 组播,同样由 packages/core/src/multicast/mod.rs 实现:
- IPv4 组播组
224.0.0.167——特意选在224.0.0.0/24范围内,因为部分 Android 设备只能收到该网段的 UDP 组播; - IPv6 组播组
ff12::fd3a:e420(link-local 作用域的临时组),作为协议 v2.2 之上的 LocalSend 扩展,与 IPv4 并行广播; - 发现不是"一问一答":设备只发送广播(announce),广播本身不等待 UDP 回应,收到广播的一方会向广播方发起 HTTP 注册请求(register)来完成握手;
- 每次广播是一个"爆发"(burst),分别在 100ms / 500ms / 2000ms 重复发送,以应对单个数据报丢失、以及刚接入网络的设备尚未就绪的情况。
这也解释了为什么防火墙规则里UDP 53317 必须放行:组播发现与 HTTP 服务器复用同一端口,缺了 UDP,设备列表就是空的。
从源码编译运行
想从源码编译 LocalSend,步骤如下:
- 安装 Flutter——直接安装,或使用 [fvm] 管理版本(项目要求的版本见 .fvmrc,当前为Flutter 3.41.9);
- 安装 Rust 工具链(仓库根目录的 rust-toolchain.toml 会约束具体版本);
- 克隆本仓库;
- 进入应用目录:
cd app; flutter pub get拉取 Dart 依赖;flutter run启动应用。
注意:LocalSend 目前要求特定(较旧)版本的 Flutter,即 .fvmrc 中列出的版本。如果你在全系统安装了别的 Flutter 版本,版本不一致就是最常见的构建失败原因。为了让开发体验一致,LocalSend 使用fvm管理项目级 Flutter 版本:安装 fvm 后,用
fvm flutter代替flutter执行后续命令。
仓库为 Rust 侧提供了独立工作区:根目录 Cargo.toml 聚合了packages/core(协议核心)、cli(命令行客户端)、server(独立服务器组件)等 crate;Flutter 与 Rust 之间通过packages/localsend_isolates中的 flutter_rust_bridge 代码绑定通信。协议文档细节可在官方协议仓库中查阅(协议实现以本仓库 packages/core 为准)。
参与贡献
翻译
LocalSend 使用 Weblate 平台管理翻译,你也可以克隆仓库手工提交翻译。翻译资源位于 app/assets/i18n 目录,包含各语种的*.json词条文件与缺失翻译清单_missing_translations_<locale>.json,Dart 侧对应的生成代码在 app/lib/gen。
注意:以
@开头的字段不是待翻译内容——它们不会被应用使用,只是给翻译者提供的文件说明性文字或上下文提示。
Bug 修复与功能改进
- Bug 修复:发现问题后,请提交带有清晰问题描述与修复方式的 PR;
- 功能改进:有改进想法时,请先提交一个 issue 讨论其必要性,再动手实现。
完整的贡献规范(含分发渠道维护说明)见 CONTRIBUTING.md。
故障排除
| 问题 | 平台(发送方) | 平台(接收方) | 解决方案 |
|---|---|---|---|
| 设备不可见 | 任意 | 任意 | 确认路由器已禁用 AP 隔离;开启时设备间通信会被阻断 |
| 设备不可见 | 任意 | Windows | 把网络配置为"专用网络"(Private);Windows 对"公用网络"限制更严格 |
| 设备不可见 | macOS / iOS | 任意 | 尝试在系统"隐私"设置中切换"本地网络"权限开关 |
| 速度太慢 | 任意 | 任意 | 使用 5 GHz Wi-Fi;在两台设备上关闭加密 |
| 速度太慢 | 任意 | Android | 已知问题(与 SAF 流实现有关),见 Flutter saf_stream 上游 issue |
结合前文的协议分析,排查"设备不可见"时可以按这条链路逐段检查:AP 隔离是否关闭 → 防火墙是否放行 TCP+UDP 53317 入站 → macOS/iOS 本地网络权限是否授予 → Windows 网络配置文件是否为 Private。"速度慢"则优先怀疑 2.4 GHz 频段拥塞,或加密带来的 CPU 开销(两端同时关闭加密可显著提速,代价是失去传输加密)。
小结
LocalSend 用一套极简但严谨的架构实现了"AirDrop 式"体验:53317 端口上 UDP 组播负责设备发现,TCP/HTTPS 负责数据传输,RSA-2048 自签名证书加 SHA-256 指纹负责设备身份与信任锚定,全程零服务器、零互联网依赖。对使用者而言,只需记住三件事——放行 53317(TCP+UDP)、关闭 AP 隔离、必要时用settings.json便携模式和--hidden参数;对开发者而言,从 .fvmrc 锁定 Flutter 版本、cd app && flutter pub get && flutter run即可本地跑通,协议细节则可直接在 packages/core 的 Rust 源码与测试中逐行验证。
【免费下载链接】localsendAn open-source cross-platform alternative to AirDrop项目地址: https://gitcode.com/GitHub_Trending/lo/localsend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考