Apple Push Notification service (APNs for short) is the centerpiece of the remote notifications feature. It is a robust and highly efficient service for propagating information to iOS and OS X devices. Each device establishes an accredited and encrypted IP connection with the service and receives notifications over this persistent connection. If a notification for an app arrives when that app is not running, the device alerts the user that the app has data waiting for it.

see more

apns-workflow

工作流程简述:

  1. Provider 将包含离线通知的报文t提交到 APNs服务器(服务器后台负责维护与APNs的通讯工作)
  2. APNs 将接收到的报文推送到具体的apple设备(APNs服务器负责与用户Apple设备的通讯工作)
  3. Apple设备 负责接收来自APNs的通知并将其关联至已安装的某个App

在以上几个个环节中:

  • 服务提供商可以控制将通知推送给哪些用户(受众),因此需要管理用户的标识性信息(即DeviceToken)
  • APNs服务器需要准确地向一个具体的Apple设备推送通知(上一步必须告知DeviceToken)

  • Apple设备接收到通知后可以精确地跳转至具体的App(第一步也必须告知其App的BundleId)

实现APNS的正确姿势:

  1. 获取安装app的iphone设备Token信息
  2. 按照APNs定义的协议与apple推送服务器通讯
  3. 采用一些容错措施提高送达率

服务承诺best effort.(这一点基本上对外声明,此服务只能用作在app未启动时有新的消息到达的提示性通知,但不可用作高实时性和一致性的场景)

Step1:获取用户设备的deviceToken

只有用户在安装某个app后点击允许接受push通知时,app服务商(provider)才能获取到DeviceToken。

Sharing the device token

Step2:Provider与APNs通讯

As a provider you communicate with Apple Push Notification service over a binary interface. This interface is a high-speed, high-capacity interface for providers; it uses a streaming TCP socket design in conjunction with binary content. The binary interface is asynchronous.

  • 正式环境 gateway.push.apple.com, port 2195
  • 开发环境 gateway.sandbox.push.apple.com, port 2195

此部分作为APNs方需鉴定Provider的合法性(因此Provider需提供签名的push证书)。通讯环节基于安全考虑启用TLS加密进行交换数据。

If you send a notification that is accepted by APNs, nothing is returned.(这句让无数国内开发者犯难,一个成功的提交不应该给一个回执吗?)

真的像开发者抱怨的那样如此不堪吗?未必见得。也许在无状态API横行的今天,开发者普遍养成的基于请求处理响应的编程模式阻碍了其更深层次的思考。TCP是可靠的传输协议(相信这一点才能好好地玩耍),长连模式下的通讯服务很多都是为了提高实时性/性能/吞吐量而设计的。trade-off阶段,就会考虑将一些没必要的outbound放弃掉(话说这样也可以减少出口带宽哦,当然真实的原因可能不止于此)。以我司自建的IM系统为例,A用户在聊天页面收到来自B用户发送的消息后,客户端即将此消息判定为已读(可是将此条消息Push下来的IM服务的Server并不知道啊),因此客户端需要在同一时刻向Server发送此条消息的已读回执,Server接收到客户端的回执报文后直接下移该用户已读消息队列的offset。Server不会额外傻呵呵的告诉客户端:哥们儿,你的回执请求我处理成功了。(换句话说,即便没处理又能怎样,用户重读一条消息,我相信很多细心的人应该都注意发现这种消息QQ、微信亦不例外)

在这个场景下,IM服务的设计者需要想明白自己的职责?提供高效地即时通讯服务,应用层协议一定从简。接收客户端已读消息回执移动下标这件事对于Server而言内存中的几个来回操作亚秒级即可完成,而向客户端告知一下处理成功这件事多长时间可以完成那就不一定了(情况远比你想象的复杂)。理解了这些,你就不会感觉苹果的nothing is returned有多么可耻了(对它而言也许就是把这条通知直接转存到cassandra那么简单,甚至是已经通过某些验证后直接给push给具体用户了,何必要告诉你成功接收了呢,young man)。

此部分的分析纯属个人见解,疏忽之处,还望各位大神拍砖(仁者见仁,智者见智)。

Feedback信息的获取则由另一个服务对外提供:

  • feedback.push.apple.com, port 2196
  • feedback.sandbox.push.apple.com , port 2196

开发实现备注

如火如荼的开源社区,早已为Provider们封装好了一些第三方推送库(可谓是前人栽树,后人乘凉)。

如何选择的第三方库?(Don’t repeat yourself!) github上apns按star降序搜索结果

感谢各位前辈把私货毫无保留的奉献出来,作为创业中的开发者在天下武功,唯快不破的拿来主义文化下即便浮躁也要记得反哺社区。

最近关注Go比较多,列举两个不错的:

Python已开发效率和部署效率而著称:PyAPNs

Java在此方面也不落后:

当然还有PHP,node,C#版,BTW,适合你的场景的就是最好的。

本文将聚焦于服务器与APNs之间的高效通讯实现细节进行详细阐述。

第一步:管理用户的deviceToken

相对于整体实现而言,此环节最简单。用户在安装App后,点击允许接收来自xx的通知时,Provider即可获取到其对应的DeviceToken信息,对其进行持久化存储(传统数据库/nosql等),管理代价并不高。

第二步:合理的维持与APNs的连接

连接的建立与维持策略

切记不要频繁的断开再重连,APNs提供的是长连服务。不断的断开再重连,会被判定DDos攻击。

维持一个合理的连接数,并且定期的check其Idea状态,这无疑是可提高推送效率的Best Practice。

第三步:按照特定的数据格式发送报文到APNs

数据格式大致如下所示:

Notification format

随着IOS8/9/10的到来,payload的大小不断被改写:

Stack overflow上的两个讨论:

What is the maximum length of a Push Notification alert text?

APN (Apple Push Notification) payload size limit

从最初的256byte到IOS8的2k再到IOS9的4k。一个新的时代在向你我走来…

wwdc2014-713

wwdc2015-720

Think more(different)

tips 离线推送作为一个基础组件存在于基础服务集群中,谨记与产品业务解耦。

  • 基础可量化的指标:吞吐量、错误率。
  • 结合业务的量化指标:覆盖范围,转化率(需要提前设计转化率定义)

谨记:用户关掉Push的那一刻起,你的app再也没有可能被你从众多沉默的App中唤醒。(潜台词:真的有必要好好地设计下产品中Push环节!!!人性化、有效性、价值输出等)在这个信息过载时代,信息不经任何筛选直接Push给用户,无疑是给用户加重负担,浪(tu)费(cai)时(hai)间(ming)。

以上说的都是错的,比方说某些特定用户对不断的受到push乐此不彼。