在老系统 Mac 上,你往往会面临一个很尴尬的选择:系统还能流畅开机,能处理文档、能跑老版 Xcode,但新版 App 却几乎装不上。尤其是 AirDrop 这类文件传输能力,在旧设备之间往往被系统版本卡死,AirDrop 对网卡、蓝牙、系统的要求又很高。于是很多老 Mac 用户只能靠 U 盘、网盘或者微信文件助手来回倒腾文件,效率和体验都很糟糕。
LocalSend 是一个很典型的局域网点对点传输工具,它的协议开放,支持 Windows、macOS、Linux、Android 等多平台互传,不像 AirDrop 那样绑定 Apple 生态。问题是官方客户端的最低系统版本早就超过了 macOS 10.7,2011 到 2013 年这一批老 Mac 基本只能看着新版本干瞪眼。为了让这些旧设备也能用上安全的本地点对点传输,我开发了一个适用于 macOS 10.7+ 的 LocalSend 客户端。
这篇文章会围绕三个层面展开:为什么老 Mac 需要自己的 LocalSend 客户端、兼容 10.7 到底要克服哪些技术障碍、以及这套客户端的核心设计思路和实践建议。如果你手上也留着旧 Mac,或者你在做跨版本兼容开发,这篇文章应该能给你不少参考价值。
1. 这篇文章真正要解决的问题
先说痛点。手头有旧 Mac 的人,大概率会碰到下面几种情况:
第一,官方 LocalSend 客户端在老系统上不可用。官方客户端基于现代跨平台框架开发,对系统版本、GPU 加速、动态库版本都有要求,系统太老时要么装不上,要么装上后界面渲染异常。频繁升级后,老版本官方包又难以下载,网上搜到的安装包版本还可能与当前系统不兼容。
第二,AirDrop 在老设备之间不可靠。AirDrop 不仅要求蓝牙和 Wi-Fi 同时可用,还要求网卡硬件支持,2011 年左右的 Mac 往往只支持旧版 AirDrop 协议,和新设备互传经常碰上“设备不可见”的玄学问题。不同系统版本的 AirDrop 兼容更是麻烦。
第三,网盘和聊天工具传输效率太低。先上传再下载的链路对局域网场景完全没有必要,尤其是传到 20GB 素材,除非有高速公网,否则等待时间完全不能接受。更重要的是,隐私数据经过第三方服务器终究会有安全风险。
所以这个项目本质上解决的是:让 macOS 10.7+ 的老设备,通过 LocalSend 开放协议,和局域网内任何支持 LocalSend 的设备,进行安全、点对点、不经过云端中转的文件传输。
这也意味着,项目的核心并不只是“画一个能互传文件的界面”,而是要在一个已经被现代开发框架抛弃的系统版本上,重新实现一套符合 LocalSend 协议规范的网络服务端、发现机制、安全校验和文件管理逻辑。这也正是这个项目最大的技术价值所在。
2. LocalSend 的核心原理与协议要点
在考虑兼容之前,先要捋清楚 LocalSend 到底是怎么工作的。
LocalSend 不是云服务,也不是基于某个公共服务器的“社交传输工具”。它的设计思路是:设备在同一个局域网内,通过 mDNS/DNS-SD 发现彼此,然后设备之间直接走 HTTPS 通道传输文件。整个流程由三个核心模块组成:
- 设备发现模块:通过 mDNS 广播自己的服务名、端口、身份指纹等信息,同时监听局域网内其他 LocalSend 设备的广播,维护一个实时设备列表。
- 安全认证模块:每个设备在本地生成自签名证书,通过 HTTPS 建立加密通道。为了防止中间人攻击,传输双方会比对证书指纹,必要的时候还需要用户手动确认。
- 文件传输模块:传输层基于 HTTP JSON API,发送方把文件元信息、分块数据通过 HTTPS 请求交给接收方,接收方在用户确认后落盘到本地目录。
用一句话概括:LocalSend 的协议把“设备发现、加密认证、文件流传输”三者拆解成标准模块,任何平台只要实现了这套协议,就能无缝接入局域网互传。
它真正聪明的地方在于,没有依赖任何中心服务器。设备只要在同一局域网,就能用本地 IP 和端口完成互动,因此即使是在没有外网的隔离环境中,LocalSend 依然可以正常工作。这对于很多办公网、家校网和没有公网条件的场景来说非常实用。
对比一下常见的传输方案会更容易理解这个项目存在的价值:
| 方案 | 是否需要中心服务器 | 传输速度 | 安全性 | 跨平台能力 | 老系统兼容性 |
|---|---|---|---|---|---|
| AirDrop | 否 | 快 | 高,但绑定 Apple 生态 | 仅 Apple 设备 | 老设备受限明显 |
| SMB/CIFS | 否 | 快,但需配置共享账号 | 中 | 强 | 老系统支持较好,但配置成本高 |
| 网盘/聊天工具 | 是 | 受公网带宽限制 | 数据经过第三方 | 强 | 客户端版本通常更高 |
| LocalSend 官方客户端 | 否 | 快 | 高,指纹+PIN 校验 | 强 | 对老 macOS 支持弱 |
| 自研 LocalSend 客户端 | 否 | 快 | 高,指纹+PIN 校验 | 强 | 可针对 10.7+ 优化 |
从这个对比能看出,LocalSend 这类协议的普适性和扩展性都很强,唯一的障碍就在客户端实现门槛。给老 macOS 做一个兼容客户端,本质上就是把协议栈在系统 API 限制下重新落地一遍。
3. macOS 10.7+ 客户端的技术选型与约束
当我决定把目标放到 macOS 10.7+ 时,第一个问题不是功能怎么做,而是用什么技术做。
最直观的思路是“套壳”官方代码库,把 Flutter 或 Electron 应用直接改造成兼容版本。但这条思路很快被否掉了,因为现代跨平台框架对系统 API 的依赖都很深,底层渲染、动态库加载、GPU 调用都要求新版系统。即使强行编译出一个包,很可能会在启动时直接崩溃,或者在老显卡上黑屏、字体异常,维护成本完全失控。
所以我选择了更朴素、也更可靠的技术路线:Objective-C/C + AppKit 原生应用,基于 POSIX socket 和 OpenSSL 静态库实现网络层。
几个关键约束讲一下:
- 不能用 Swift。Swift 语言运行时和编译链在 macOS 10.7 上的支持非常有限,要在老系统上跑 Swift 代码,光是 runtime 兼容就够折腾。Objective-C 从 NeXT 时代就是 macOS 的官方语言,在 10.7 上非常成熟,头文件和系统框架兼容性最好。
- 不能依赖较新的 AppKit API。比如新版按钮样式、新版文件预览、Touch Bar 相关接口都要避开。界面要尽量用 10.7 时期就存在的控件,即使不好看,至少稳定。
- TLS 协议栈必须自己管。macOS 10.7 自带的 Secure Transport 在 TLS 版本、密码套件上都有历史包袱。LocalSend 现代客户端要求 TLS 1.2 甚至更高,旧系统默认栈很可能导致握手失败。因此我在项目里静态链接了 OpenSSL 兼容版本,用 OpenSSL 自己拉起 HTTPS server,保证能跟新版客户端互通。
- 资源有限,少用动态库。10.7 系统自带的动态库版本非常固定,导出符号也少,外部依赖最好全部静态链接,否则用户换一台机器就可能出现“加载不到库”的问题。
- 架构目标以 x86_64 为主。macOS 10.7 虽然也支持部分 32 位运行,但 2011 年后的 Mac 大部分已经是 64 位 CPU,直接只打 x86_64 包可以显著减少代码体积和兼容测试面。
从这些约束可以看出,这不是一个“写新功能”的项目,更像是“在受限环境里做减法”的工程。但正是这种约束,逼迫我去仔细研究 LocalSend 协议本质,而不是直接依赖别人的框架实现。
如果你也要做类似的老系统兼容项目,我会建议你先把第三方依赖压缩到最小。每引入一个依赖,都可能引入一个与老 SDK 不兼容的隐藏问题。
4. 开发环境与最小工程配置
开发面向 macOS 10.7+ 的客户端,环境搭建分为两步:
- 准备一台可安装对应老版本 SDK 的 macOS 环境,或者准备一台能运行老系统的虚拟机。
- 搭建可复现的构建脚本,建议用 CMake/Makefile 组织工程,避免完全依赖某个图形化 IDE 工程文件。
这里不写死具体 SDK 版本,因为不同情况下你手头的 Xcode 版本可能不一样。关键是设置好部署目标(Deployment Target)和架构,确保编译产物能在 10.7 上运行。
我用 CMake 组织工程,最小配置如下:
# 文件路径:CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(LocalSendClient LANGUAGES C OBJC) # 最低支持 macOS 10.7,仅构建 x86_64 set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") set(CMAKE_OSX_ARCHITECTURES "x86_64") # 可执行文件 add_executable(LocalSendClient src/main.m src/AppDelegate.m src/DiscoveryService.m src/CryptoHelper.c src/TransferServer.c src/TransferClient.c ) # 静态链接 OpenSSL,降低部署风险 find_package(OpenSSL REQUIRED) target_include_directories(LocalSendClient PRIVATE ${OPENSSL_INCLUDE_DIR}) target_link_libraries(LocalSendClient PRIVATE ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY} "-framework AppKit" "-framework Foundation" ) # 对 10.7 老系统禁用 ARC 依赖的高级特性,代码使用手动引用计数 target_compile_options(LocalSendClient PRIVATE -fno-objc-arc)这里有个值得注意的点:我给代码关闭了 ARC,也就是不启用自动引用计数。原因不是 ARC 不好,而是老系统的运行时对 ARC 某些对象生命周期的处理存在边缘 bug,手动引用计数在这种兼容性项目里更可控。
构建时执行:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release产物是一个LocalSendClient.app包。之后用codesign签名,或者至少在 DMG 中附上校验值,方便用户确认文件完整。
对于需要下载 macOS 10.7 或更高版本系统镜像来搭建测试环境的用户,建议只从官方支持渠道获取安装器,不要在来源不明的下载站随便拿镜像,尤其是涉及系统重装的场景,镜像可信度比什么都重要。老系统重装本身并不复杂,麻烦的是装完系统后,找一款能用的局域网传输工具,这也是这个客户端被开发的直接动力。
5. 核心模块实现与关键代码
5.1 设备发现模块:基于 Bonjour/mDNS
macOS 从很早开始就提供了 Bonjour 网络发现框架,NSNetService和NSNetServiceBrowser在 10.7 时代已经非常稳定。因此设备发现模块可以直接用系统框架实现,不需要引入第三方库。
核心思路是:应用启动时启动一个 HTTPS 服务,监听某个端口,然后通过 mDNS 广播服务。别的设备通过 Bonjour 浏览器发现这个服务后,再读取 TXT 记录中的指纹、设备名称、协议版本等信息。
// 文件路径:src/DiscoveryService.m #import "DiscoveryService.h" static NSString * const kServiceType = @"_localsend._tcp."; @implementation DiscoveryService - (void)startAdvertisingWithName:(NSString *)deviceName port:(NSInteger)port fingerprint:(NSString *)fingerprint { self.netService = [[NSNetService alloc] initWithDomain:@"local." type:kServiceType name:deviceName port:(int)port]; self.netService.delegate = self; // 通过 TXT 记录广播指纹和设备信息,后续连接时用于身份比对 NSDictionary *txtDict = @{ @"deviceName": deviceName, @"fingerprint": fingerprint, @"protocolVersion": @"1.0", @"port": [NSString stringWithFormat:@"%ld", (long)port] }; self.netService.TXTRecordData = [NSNetService dataFromTXTRecordDictionary:txtDict]; [self.netService publish]; } - (void)startBrowsing { self.browser = [[NSNetServiceBrowser alloc] init]; self.browser.delegate = self; [self.browser searchForServicesOfType:kServiceType inDomain:@"local."]; } #pragma mark - NSNetServiceBrowserDelegate - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing { [service resolveWithTimeout:3.0]; } #pragma mark - NSNetServiceDelegate - (void)netServiceDidResolveAddress:(NSNetService *)service { NSString *host = service.hostName; NSInteger port = service.port; NSDictionary *txt = [NSNetService dictionaryFromTXTRecordData:service.TXTRecordData]; NSString *fingerprint = txt[@"fingerprint"]; NSString *deviceName = txt[@"deviceName"] ?: service.name; // 交给业务层创建连接,并缓存指纹信息 if ([self.delegate respondsToSelector:@selector(discoveryService:didDiscoverDevice:host:port:fingerprint:)]) { [self.delegate discoveryService:self didDiscoverDevice:deviceName host:host port:port fingerprint:fingerprint]; } } @end这一段逻辑看起来不复杂,但它解决的是整个传输链路的第一步:设备之间如何互相找到。实际运行时你会发现,mDNS 依赖网络环境,某些办公室三层交换机或 AP 默认会过滤组播包,导致设备发现失败。所以在实现时,我会额外加一个“手动输入 IP”的兜底入口,这在排障时非常重要。
5.2 安全认证模块:自签名证书与指纹校验
LocalSend 自己用的是 HTTPS 协议栈,设备之间传输文件时通过自签名证书完成 TLS 握手。问题是,如果客户端直接无条件信任自签名证书,就相当于给中间人攻击开了后门。更安全的做法是:把证书指纹预先通过 mDNS 广播出来,连接建立后比对对端证书指纹,一致才继续传输。
在 macOS 10.7 时代,OpenSSL 的静态库方案比纯系统框架更可控,因为我可以自己决定 TLS 版本和密码套件。核心代码大致是这样:
// 文件路径:src/CryptoHelper.c // 提取对端证书指纹,并与 mDNS 广播的 expected 做比对 #include <openssl/x509.h> #include <openssl/ssl.h> #include <openssl/evp.h> #include <stdio.h> #include <string.h> static int verify_fingerprint(SSL *ssl, const char *expected) { X509 *cert = SSL_get_peer_certificate(ssl); if (!cert) { return -1; // 获取证书失败 } unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len = 0; if (!X509_digest(cert, EVP_sha256(), digest, &digest_len)) { X509_free(cert); return -2; // 指纹计算失败 } X509_free(cert); char actual[EVP_MAX_MD_SIZE * 3 + 1] = {0}; for (unsigned int i = 0; i < digest_len; i++) { if (i > 0) { strcat(actual, ":"); } char part[4]; snprintf(part, sizeof(part), "%02X", digest[i]); strcat(actual, part); } // 实际生产环境建议使用 CRYPTO_memcmp 做常量时间比较,防止时序侧信道 if (strcmp(actual, expected) == 0) { return 0; // 校验通过 } return -3; // 指纹不一致,可能中间人攻击 }这段代码解决了兼容实现里最容易踩坑的安全问题。对端连接时,如果指纹不一致,整个连接直接中断,而不是像很多简易传输工具那样,把安全校验完全交给用户判断。毕竟普通用户根本分不清“证书不受信任”和“有人正在篡改你的传输”这两者之间的区别。
5.3 文件收发模块:大文件流式处理
文件传输是另一个容易做砸的地方。最笨的办法是把整个文件读进内存再 POST 给对端,这在传小文件时没什么问题,但传 10GB 素材时内存直接爆掉。因此,实现上必须走流式传输。
// 文件路径:src/TransferClient.m // 发送文件时,使用流式读取,避免内存占用过高 - (void)sendFile:(NSString *)filePath toDevice:(DeviceInfo *)device { NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:filePath]; if (!inputStream) { NSError *error = ...; [self.delegate transferDidFailWithError:error]; return; } NSURL *url = [device uploadURLWithFileName: [filePath lastPathComponent] size: [self fileSizeAtPath:filePath]]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; request.HTTPBodyStream = inputStream; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; [connection start]; }文件接收端则相对复杂,需要先解析 JSON 元信息,确认文件名、大小,再落盘临时文件,最后在完整接收后做一次校验并移动到 Downloads 目录。全程必须用独立线程处理,不能让文件拷贝阻塞 UI 界面。
如果你做过类似项目,应该知道这里真正的难点并不在传输 API,而在于状态管理。发送方和接收方都有多种状态:等待确认、拒绝、取消、暂停、传输中、传输完成、校验失败。这些状态如果只靠散落的全局变量管理,一旦出现断网、用户取消、磁盘写入失败,很容易卡死或状态错乱。我在项目里用一个枚举状态机管理整个传输生命周期,代码结构清楚很多,后面排查问题也快。
6. 编译、运行与功能验证
在 macOS 10.7+ 上编译并运行客户端后,验证流程可以按下面这套路径走。
首先,启动客户端,确认日志里出现 HTTPS 服务监听成功的提示:
[INFO] LocalSend HTTPS server started on port 53317 [INFO] Advertising service via Bonjour... [INFO] Browsing LocalSend peers...随后,在同一局域网中,用另一台可运行官方 LocalSend 的设备发起“发送文件”操作。正常情况下,官方客户端设备列表里会马上出现老 Mac 的设备名。
再反向验证一次:从老 Mac 客户端向官方设备发送文件,核心检查项包括:
- 设备发现是否正常;
- 指纹校验提示是否出现;
- 文件接收前是否要求用户确认;
- 传输大文件时 UI 是否保持响应;
- 中断传输后再次重试,两端状态是否正确复位。
我自己的验证矩阵大致如下:
| 验证场景 | 操作方式 | 预期结果 |
|---|---|---|
| 同网段双向发现 | 两端开启客户端,观察设备列表 | 3 秒内互相可见 |
| 自签名证书校验 | 局域网内传输第一个文件 | 弹出指纹/PIN 确认,确认后才开始 |
| 多个小文件连续传输 | 同时选择 10 个小文件 | 全部进入队列,顺序完成 |
| 大文件传输 | 发送 5GB 单文件 | UI 不卡死,可看到进度条稳定推进 |
| 取消与重试 | 传输中点击取消,再次发送 | 双方状态正常复位,临时文件被清理 |
| 跨网段兜底 | 手动输入对端 IP 和端口 | 可绕过 mDNS 组播限制完成互传 |
从实际经验看,绝大多数失败都不是代码本身的问题,而是网络环境限流、防火墙拦截、或者两端时间不一致导致的 TLS 握手失败。因此,验证环节不要只跑“同一台路由器下互传”,最好也测试一下跨交换机、跨 AP 的网络环境。
7. 常见问题与排查思路
做一个兼容 10.7 的客户端,最大的工作量其实有一部分在排错上。下面这张表汇总了我开发过程中最容易遇到的问题,以及对应的排查路径。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 设备发现列表为空 | mDNS 组播被防火墙或交换机过滤 | 检查两端是否在同一网段;关闭系统防火墙测试 | 增加手动输入 IP 的兜底入口,并提示用户检查 AP 的组播设置 |
| 连接成功但立刻断开 | 对端证书指纹校验失败 | 在日志中确认指纹是否一致 | 重新生成证书,或检查 TXT 记录中的指纹是否和实际证书匹配 |
| TLS 握手失败 | 老系统 OpenSSL 版本过旧,不支持现代密码套件 | 抓包看 ClientHello,确认 TLS 版本和密码套件 | 静态链接新版 OpenSSL,并保留可配置的密码套件开关 |
| 大文件传一半卡死 | 磁盘写入慢或系统休眠 | 查看日志中最后写入偏移量 | 接收端添加流式写盘,并在传输期间禁用系统休眠 |
| 收到文件但无法打开 | 文件元信息解析错误,扩名或尺寸错误 | 对比发送方和接收方的文件哈希 | 传输完成后增加 SHA-256 校验,失败则自动重传 |
| 客户端在 10.7 上无法启动 | 部署目标或架构设置不正确 | 使用 otool 检查 binary 平台版本 | 在 CMake 中固定 CMAKE_OSX_DEPLOYMENT_TARGET 和 ARCHITECTURES |
排查时有一个通用原则:先看日志,不要乱猜。因为老系统上的网络错误提示往往不直观,你以为是对端拒绝连接,实际上可能是防火墙拦截,也可能是时间偏差导致 TLS 证书验证失败。日志里把每一层的关键节点打出来,就能快速缩小问题范围。
另外,当遇到“客户端和服务端状态不一致”这类看似复杂的问题时,最简单的做法是把两端客户端完整重启,清空临时文件,重新建立会话。很多状态错乱问题本质上都是状态机没有处理好,重启能帮你确认是否存在脏状态残留。
8. 工程化与最佳实践建议
这类兼容老系统的客户端,绝不能只做到“能跑”就结束。从工程化角度看,下面几条建议非常值得沉淀。
第一,锁定协议版本基线。LocalSend 作为一个开源生态,协议本身还在迭代。我在实现这个老系统客户端时,会固定兼容某个协议版本,而不是时刻追赶官方最新改动。否则官方某个接口调整,会让整个老系统客户端立刻失效。锁版本的同时,把协议解析逻辑放到独立模块,后续做协议升级时只替换核心模块,不会影响 UI 和文件管理逻辑。
第二,安全校验只能收紧,不能放开。有人会觉得,既然自签名证书在老系统上握手问题这么多,干脆直接关闭证书验证好了。这是一个非常危险的想法。LocalSend 的核心价值之一,就是点对点传输不会被人窥探。如果因为便捷而放开 TLS 验证,等于把整个传输过程暴露在局域网劫持风险之下。更合理的做法是,保留指纹校验,并允许用户在 UI 上显示清晰的指纹供双方比对。
第三,默认下载目录与文件覆盖策略要谨慎。接收方默认不要自动覆盖同名文件,建议生成新的文件名,例如"文件名(1).pdf"。否则用户接收多个相似文件时,很可能出现文件被覆盖而无法找回的情况。文件落盘前先写入临时目录,接收完成后通过 rename 到最终路径,也避免传输中断残留半截文件。
第四,为老系统准备清晰的安装说明。老系统用户往往不是专业开发者,可能连如何执行 codesign 信任都不熟悉。安装包最好以 DMG 形式提供,里面放一个说明文件,引导用户如何打开“安全性与隐私”允许应用运行。不要默认用户会命令行操作,尽量把所有需要点选的步骤都放到安装包里。
第五,做好日志与崩溃收集。10.7 上很多问题只能在真机环境复现,如果日志模块做得太弱,用户遇到问题后,你拿不到任何线索,基本只能盲猜。我在项目里把日志写到~/Library/Logs/LocalSendClient/目录,并附带启动时间、系统版本、内存占用等基础指标,后续排查效率会高很多。
第六,处理好系统数据占用问题。网上经常看到 macOS 用户抱怨“系统数据占用过大”,在老系统上这个问题尤其明显,因为旧版本系统的缓存清理机制不够完善。文件共享客户端如果频繁产生临时文件、断点记录、缩略图缓存,会进一步加重系统负担。因此,我在接收端设置了“传输完成后自动清理临时文件”的策略,避免用户使用一段时间后,磁盘被隐藏临时文件塞满。
9. 总结与后续方向
这个项目让我最深的一点体会是:兼容老系统不是简单地把编译版本调低,而是要在 API 限制、网络协议、安全机制三者之间做权衡。为了让 macOS 10.7+ 的老设备用上 LocalSend,我放弃了现代 UI 框架,改用 Objective-C/C 重建核心栈;为了安全,我保留了自签名证书指纹校验;为了跨设备互传稳定,我严格区分了设备发现、安全认证、文件传输三个独立模块。
如果你也在做类似的项目,接下来可以重点关注三个方向:
- 断点续传与哈希校验。大文件传输的稳定性永远是痛点,加入断点续传后,用户体验会有明显提升。
- IPv6 与多网卡支持。很多现代局域网设备开始走 IPv6 互访,当前客户端仍以 IPv4 为主,后面对端协议要做适配。
- UI 可访问性优化。老 Mac 用户中不少人只是希望拿旧电脑当备用机或家庭服务器,界面越简单越好,甚至很多人希望有一个“仅接收模式”,连发送界面都不用打开。
如果你手头有一台吃灰的旧 Mac,不妨先装一个 LocalSend 服务端试试,感受一下局域网点对点传输的便利。如果你想参与这个客户端的开发或测试,也欢迎从协议层入手,先把 LocalSend 官方协议文档读透,再看本项目的源码,你很快会明白为什么有些地方必须绕过现代框架做原生实现。