4、APNs(Apple Push Notification service)原理:APNs架构、Provider-Device-APNs通信流程、Token的生成与作用

推送通知这件事,说白了就是让苹果的服务器帮你喊一嗓子:「嘿,你的App有新消息了!」。但这一嗓子背后,其实有一套非常严谨的架构在支撑。我刚开始接触推送时,总觉得APNs就是个黑盒子,直到有一次线上推送大面积失败,我才被迫把它里里外外研究了个透。

今天咱们就把APNs的底裤扒干净。从架构设计,到通信流程,再到那个神秘的Token,一个一个说清楚。

APNs的整体架构

APNs不是单打独斗的。它由三个角色组成:

  • Provider:也就是你的后端服务器。它负责把推送消息发给苹果。
  • APNs:苹果的推送网关。它负责验证消息,然后转发给对应的设备。
  • Device:用户的iPhone或iPad。它收到推送后,交给系统处理。

这三者之间,是典型的「请求-转发-响应」模型。但有个关键点:APNs不会主动找你的设备。设备必须先跟APNs建立一条长连接,APNs才能把消息推过来。

核心要点:APNs本质上是一个消息中继站。它不存储你的业务数据,只负责把Provider发来的Payload,准确投递到目标设备上。

Provider-Device-APNs通信流程

整个流程分三步走。我习惯把它叫做「握手-投递-确认」三部曲。

第一步:设备注册,获取Token

App启动时,会调用 registerForRemoteNotifications()。系统会跟APNs建立一条加密连接,然后APNs返回一个设备Token。这个Token是啥?说白了就是设备的「身份证」。

// 注册推送
UIApplication.shared.registerForRemoteNotifications()

// 获取Token的回调
func application(_ application: UIApplication, 
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    // 把deviceToken转成字符串,发给你的后端
    let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    // 上传到你的Provider
    uploadTokenToServer(tokenString)
}

嗯,这里要注意:Token是会变的。用户重装App、恢复备份、甚至系统升级,都可能导致Token变化。所以每次启动都重新获取一次,是最稳妥的做法。

第二步:Provider发送推送请求

你的后端拿到Token后,就可以构造推送请求了。APNs的接口是HTTP/2的,请求格式如下:

POST /3/device/{device_token} HTTP/2
Host: api.push.apple.com
Authorization: bearer {provider_token}
apns-topic: com.yourcompany.yourapp

{
    "aps": {
        "alert": {
            "title": "新消息",
            "body": "你有一条未读通知"
        },
        "sound": "default",
        "badge": 1
    }
}

这里有个坑:Provider TokenDevice Token 是两码事。Provider Token是你的服务器向APNs证明身份的凭证,用JWT格式签发。Device Token则是标识目标设备的。

我曾经踩过的坑:有一次推送死活发不出去,查了半天发现是Provider Token过期了。JWT的有效期默认只有1小时,你的后端必须实现自动续签机制。否则一到整点,所有推送全部哑火。

第三步:APNs投递到设备

APNs收到请求后,会做两件事:

  1. 验证:检查Provider Token是否有效,Device Token是否合法,Payload格式是否正确。
  2. 投递:如果一切正常,APNs会通过设备之前建立的长连接,把消息推过去。

如果设备在线,消息秒到。如果设备离线(比如飞行模式),APNs会帮你存着。等设备上线后,再补推。但注意,APNs的存储是有期限的,默认只保留30天。超过30天还没投递成功,消息就丢了。

Token的生成与作用

Token这个东西,很多人以为就是个随机字符串。其实它的生成过程,比你想的要复杂得多。

Token是怎么生成的?

Token是APNs根据设备的 硬件信息App的Bundle ID,通过加密算法生成的。具体来说:

  • 它包含了设备的唯一标识(但不是UDID,苹果早就废弃UDID了)
  • 它绑定了你的App的Bundle ID
  • 它使用APNs的私钥进行签名,防止伪造

所以,同一个设备上,不同App拿到的Token是不一样的。同一个App,在不同设备上拿到的Token也不一样。这就保证了Token的 唯一性安全性

Token的作用是什么?

说白了,Token就是APNs用来定位设备的「门牌号」。你的Provider发推送时,必须带上这个门牌号,APNs才知道把消息送到哪里去。

但Token还有一个隐藏作用:安全校验。APNs在投递消息前,会验证Token是否合法。如果Token被篡改过,或者已经过期,APNs会直接拒绝投递。这能防止恶意第三方伪造推送。

个人建议:Token在传输过程中一定要加密。我见过有人直接用HTTP明文传输Token,这等于把家门钥匙挂在门口。你的后端应该使用HTTPS,并且对Token做额外的签名校验。

APNs的通信细节

聊点更深入的。APNs的通信协议,在iOS 13之后有了重大变化。

特性 旧版(iOS 12及之前) 新版(iOS 13+)
连接方式 TCP长连接(二进制协议) HTTP/2
认证方式 证书(p12) JWT Token
推送确认 无(发完即忘) 有(APNs返回状态码)
多主题支持 一个连接一个Topic 一个连接多个Topic

你想想看,HTTP/2的好处是什么?多路复用!你的后端只需要跟APNs建立一条连接,就能同时处理多个App的推送。这在旧版二进制协议下是做不到的。

另外,新版协议支持 推送确认。APNs会返回一个HTTP状态码,告诉你推送是否成功。比如:

  • 200:推送成功
  • 400:Payload格式错误
  • 403:认证失败(Token过期或无效)
  • 404:Device Token不存在
  • 429:请求太频繁,被限流了

我个人习惯,在后端日志里把这些状态码都记录下来。一旦出现大量403或404,就说明Token管理出了问题,需要及时排查。

避坑指南

最后,分享几个我亲身踩过的坑:

  • Token不要缓存太久:我曾经把Token存在本地数据库里,一个月没更新。结果用户换了手机,推送全发到旧设备上去了。正确的做法是每次启动都重新获取Token,并同步到后端。
  • Provider Token要提前续签:JWT过期前5分钟,就应该生成新的Token。别等到过期了再生成,中间那几秒的空白期,推送会全部失败。
  • 注意推送频率限制:APNs对每个设备有速率限制。短时间内发太多,会被限流。我遇到过运营同事一口气发10万条推送,结果被APNs封了半小时。

嗯,APNs的原理其实不复杂。但细节决定成败。把Token管理好,把认证机制做扎实,推送这条路就能走得稳。下一章,咱们聊聊Payload的构造技巧,看看怎么让推送消息更「聪明」。