ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AirSync协议开发者指南:从零构建自己的AirSync客户端(WebSocket与23种JSON消息类型全解)

AirSync协议开发者指南:从零构建自己的AirSync客户端(WebSocket与23种JSON消息类型全解)

【免费下载链接】airsync-mac

AirSync macOS - Bring the forbidden macOS continuity to Android

项目地址:https://gitcode.com/gh_mirrors/ai/airsync-mac
点击查看免费下载

本文深入解析AirSync 协议:基于一条 WebSocket 长连接与 23 种 JSON 消息类型,带你从零构建自己的 AirSync 客户端——理解 Android 与 Mac 之间的通知镜像、文件传输、媒体控制等核心通信机制,以及 AES-GCM 加密与心跳保活的完整实现。

1. AirSync 是什么?

AirSync是一款开源的 macOS 应用,目标是把 Apple "Continuity(接力)" 体验带到 Android 手机:手机通知同步到 Mac 桌面、音乐状态镜像到控制中心、Mac 反向控制手机媒体、双端文件互传、剪贴板同步等。

它的通信核心并不复杂:

  • Mac 端运行一个WebSocket Server(基于 Swifter 库)
  • Android 端作为WebSocket Client主动连接
  • 所有业务数据都封装成统一的 JSON 消息,走 AES-GCM 加密通道

💡 如果你想在本地编译源码,可以克隆仓库:git clone https://gitcode.com/gh_mirrors/ai/airsync-mac

2. 协议总览:一条连接 + 一个信封

整个协议可以浓缩成两句话:连上ws://<Mac的IP>:6996/socket,然后收发{"type": "消息类型", "data": {...}}这样的 JSON 信封。

关键参数值说明
默认端口6996定义于 Constants.swift,可在设置页修改
WebSocket 端点/socket注册于 WebSocketServer.swift
消息信封{"type": "...", "data": {...}}全部消息的统一结构
传输加密AES-GCM (256-bit)密文 Base64 后作为文本帧发送
心跳间隔4.5 秒无活动 20 秒判定会话失效

消息信封由两个字段组成,类型枚举完整定义在 Message.swift,data字段采用"万能解码器"策略(见 AnyDecodable.swift),允许各消息类型自由携带不同结构的 JSON 对象:

{ "type": "status", "data": { "battery": { "level": 87, "isCharging": true }, "isPaired": true } }

3. 第一步:发现 Mac(Bonjour 广播)

你的客户端首先需要"找到"Mac。AirSync 使用Bonjour(mDNS)做局域网服务发现,实现在 BonjourServiceAdvertiser.swift:

发现项内容
服务类型_airsync._tcp
服务名AirSync-<设备名>
TXT 记录id(稳定 UUID)、name、port、ver

在 macOS 或任意支持 mDNS 浏览的工具中搜索_airsync._tcp即可定位到 Mac 的 IP 与端口,随后直接发起 WebSocket 连接。此外项目还内置了 UDP 广播(UdpBroadcastDiscovery.swift)作为补充发现手段。

4. 握手:device与macInfo消息对

连接建立后,第一条消息必须是device类型的手shake,完成设备注册。核心字段如下:

{ "type": "device", "data": { "name": "Pixel 9", "id": "12345abc", "ipAddress": "192.168.1.50", "port": 8765, "version": "2.0.0", "adbPorts": ["5555"], "wallpaper": "base64...", "bleAuthToken": "token...", "targetIpAddress": "192.168.1.20" } }

Mac 收到后会回一条macInfo消息,携带 Mac 的名称、机型(modelIdentifier)、版本、是否 Plus 订阅以及已保存的 Android 应用包列表(见 WebSocketServer+Handlers.swift)。

⚠️主会话机制:Mac 只把第一个连接视为primarySession,除device外的所有消息只接受来自主会话(WebSocketServer+Handlers.swift)。若新的本地网段会话(192.168./10./172.16-31)发起握手,会强制接管并断开旧会话——这是为了"局域网优先"的稳定性设计。

5. 23 种 JSON 消息类型全解

当前源码的MessageType枚举共定义了 31 个值,其中消息路由器主动分发处理 23 种(handleMessage 的 switch 分支),其余 8 种主要是 Mac 主动发给 Android 的出站类型。按功能分组如下:

🔌 连接与状态(3 种)

类型方向核心 data 字段
deviceAndroid→Macname / id / ipAddress / port / version / wallpaper
status双向battery / isPaired / music(播放状态、专辑封面、进度)
userDisconnectedAndroid→Mac用户主动断开前发送,Mac 据此走"手动断开"流程

🔔 通知镜像(5 种)

类型方向作用
notificationAndroid→Mac推送通知:id / title / body / app / package / priority / actions / progress / ongoing
notificationActionMac→Android在 Mac 上点通知按钮,转发操作(如"回复")
notificationActionResponseAndroid→Mac操作执行结果(id / action / success)
notificationUpdateAndroid→Mac通知内容更新或消失(action=dismiss)
dismissalResponseAndroid→Mac取消通知的结果回执

🎵 媒体与通话(7 种)

类型方向作用
macMediaControlAndroid→Mac用 Android 控制 Mac 的音乐:play / pause / next / previous / stop / playPause
macMediaControlResponseMac→AndroidMac 侧执行结果回执
mediaControlResponseAndroid→MacAndroid 侧媒体操作结果回执
callEventAndroid→Mac来电/去电事件:eventId / number / direction / state / contactName
callProgressAndroid→Mac通话中进度更新(如振铃转接通)
callControlMac→AndroidMac 上接听/挂断:accept / decline / end
callControlResponseAndroid→Mac通话控制结果(action / success / message)

📋 应用与剪贴板(2 种)

类型方向作用
appIconsAndroid→Mac同步应用列表:{包名: {name, icon(base64), systemApp, listening}},Mac 会落盘缓存图标
clipboardUpdateAndroid→Mac剪贴板文本同步(text 字段)

📁 文件传输(6 种)

类型方向作用
fileTransferInit双向开始传输:id / name / size / mime / chunkSize / checksum / isClipboard
fileChunk双向数据分片:id / index / chunk(base64)
fileChunkAck双向分片确认:id / index
fileTransferComplete双向传输结束,触发大小与 SHA256 校验
transferVerifiedAndroid→Mac接收方校验结果(verified 布尔值)
fileTransferCancel双向取消本次传输(id)

消息模板的构造逻辑集中在 FileTransferProtocol.swift。

🖱️ 远程控制与文件浏览(6 种)

类型方向作用
remoteControlAndroid→Mac用 Android 遥控 Mac:键盘(keypress / type)、鼠标(mouse_move / mouse_click / mouse_scroll)、音量、锁屏、屏保、亮度等 20+ 动作
browseLsMac→Android请求列出 Android 目录(path / showHidden)
browseDataAndroid→Mac返回目录项列表:items: [{name, isDir, size, time}],出错时返回error

remoteControl的完整动作表可参考 handleRemoteControl。

📤 其余 8 种:Mac 出站的"另一半协议"

枚举中剩余的值——macInfo、volumeControl、macVolume、toggleAppNotif、wakeUpRequest、macMediaControlResponse等——主要由 Mac 发出(如volumeControl让 Android 调音量、browseLs拉取目录),以及少量双向复用类型。客户端实现时只需把 Mac 端 WebSocketServer+Outgoing.swift 里的sendMessage调用当作"待实现消息清单"逐一对照即可。

6. 加密层:AES-GCM 如何工作

协议的所有消息默认经过AES-GCM 256-bit加密(CryptoUtil.swift):

  1. Mac 首次启动时随机生成 256-bit 密钥,Base64 后持久化保存
  2. 发送时:AES.GCM.seal→ 得到nonce(12B) + 密文 + tag(16B)的组合字节 → Base64 → 作为文本帧发送
  3. 接收时:Base64 解码 →AES.GCM.open还原出 JSON 明文

调试技巧:Mac 端在未加载密钥时会以明文收发消息(WebSocketServer.swift),开发阶段可以先用明文打通流程,再接加密。

7. 心跳机制:4.5 秒 Ping,20 秒超时

保活逻辑在 WebSocketServer+Ping.swift:

  • Mac 每4.5 秒发送{"type":"ping","data":{}}
  • 客户端应回一条{"type":"status","data":{"type":"pong"}}(Mac 收到后直接忽略,但任何入站文本帧都会刷新活跃时间戳)
  • 超过12 秒无活动 → 标记"弱连接"(UI 提示)
  • 超过20 秒无活动 → 判定会话失效,自动重启服务并重新广播 Bonjour 发现,Android 会重新发现并连接

这意味着你的客户端只要定期"说话"(哪怕回一个空消息),连接就能一直保持。

8. 文件传输协议:滑动窗口 + 校验

文件传输是协议中最精巧的部分(sendFile),流程为:

Init(id, name, size, mime, chunkSize, checksum) → Chunk(index 0) → Ack(0) → Chunk(index 1) → Ack(1) ← 滑动窗口大小 = 8 → ... → Complete(id, size, checksum) → Verified(id, verified: true)
参数默认值说明
chunkSize64 KB每个分片的字节数
窗口大小8最多 8 个分片"在途",收到 Ack 后推进
Ack 超时2000 ms超时重发,最多重试 3 次
完整性SHA256Complete 时校验,落盘到 Downloads 并自动去重重命名

接收端(Mac 侧)按index * chunkSize偏移写入临时文件(handleFileChunk),所以分片乱序到达也没关系。

9. 从零构建你的客户端:5 步清单

✅第 1 步|建立连接:通过 Bonjour 找到_airsync._tcp服务,连接ws://<IP>:6996/socket

✅第 2 步|完成握手:发送device消息(name / id / ipAddress / port 必填),等待macInfo回复

✅第 3 步|保持心跳:收到ping后回status/pong,并每几秒主动推送一次status(电量 + 音乐状态)

✅第 4 步|订阅核心事件:处理notification(渲染通知卡片)、callEvent(来电弹窗)、appIcons(应用网格)

✅第 5 步|实现文件传输:按 Init → Chunk/Ack → Complete → Verified 四步状态机收发文件,记得做 SHA256 校验

一个最简的"能跑起来"的客户端,实际上只需要正确处理device、status、ping三类消息,Mac 端就会认为配对成功并进入主页。

10. 常见坑与调试建议

坑原因与解决
消息发了没反应你不在主会话上——非 primary 会话除device外的消息一律被丢弃,先确认连接顺序
数字精度丢失JSON 数字可能被解析为 Int/Double 混排(如音乐的 duration/position 以毫秒计的整型),建议按数值统一处理
进度条"漂移"status.music.position是快照值,协议携带positionTimestamp做类 NTP 的时间补偿,播放中应自行累加
连接突然重启Mac 检测到 IP 变化(切换 WiFi/热点)会 5 秒后重启服务并刷新二维码,客户端要能自动重连
大文件传输卡住确认按 index 顺序推进窗口、及时回 Ack,2 秒超时会触发重发

11. 源码文件速查

模块路径
消息类型枚举与信封定义Message.swift
WebSocket 服务主体(路由/密钥/重启)WebSocketServer.swift
23 种消息的分发处理WebSocketServer+Handlers.swift
出站消息与文件发送WebSocketServer+Outgoing.swift
Ping 心跳与超时WebSocketServer+Ping.swift
网络适配与 IP 变更监控WebSocketServer+Networking.swift
AES-GCM 加解密CryptoUtil.swift
文件传输消息模板FileTransferProtocol.swift
Bonjour 服务广播BonjourServiceAdvertiser.swift
默认端口等常量Constants.swift

结语

AirSync 协议的精髓在于简单:一条 WebSocket、一种 JSON 信封、23 种主动处理的消息类型、一套 AES-GCM 加密和 4.5 秒心跳,就支撑起了通知镜像、来电提醒、文件互传、远程桌面控制等完整的跨设备体验。理解了device握手、status心跳与文件四步状态机这三块骨架,你的自定义客户端就已经具备接入 AirSync 生态的全部能力——剩下的,只是挑你想要的那些消息类型去实现而已。

【免费下载链接】airsync-mac

AirSync macOS - Bring the forbidden macOS continuity to Android

项目地址:https://gitcode.com/gh_mirrors/ai/airsync-mac
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进