AlexaZhou!

Here's my blog :)

iOS推送开发记录

背景

由于iOS独特的设计,iOS设备对于应用程序在后台运行有诸多限制。因此,当用户切换到其他程序后,原先的程序无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。

为解决这一限制,苹果推出了APNs(苹果推送通知服务)。APNs 允许设备与苹果的推送通知服务器保持常连接状态。当你想发送一个推送通知给某个用户的iPhone上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。

原理

每一台iOS设备启动后,都会和APNs建立TCP长连接。当我们需要推送消息给自己的应用程序时,需要连接并把消息发送给APNs,然后APNs会转发消息给对应iOS设备。对应设备收到消息后,iOS系统会通知对应的App,这样就完成了一次推送。

image

好处是不管iOS设备上有多少个App需要接受推送,iOS设备都只需要维持一个统一的TCP长连接,这样对节省电量来说是很有利的。

申请证书

网上有很多,此处略过

导出证书

这里需要导出两个部分,一个部分是证书,另外一个部分是key。打开钥匙串管理工具,找到推送证书,点证书前面的三角展开可以看到key,也就是私钥。

  • 在推送证上点击右键,选择导出,注意导出时设置为p12格式,密码为空即可,保存为cert.p12
  • 在key上点击右键,导出为key.p12

经过这一步,就得到了连接APNs所需要的证书。

转换证书格式

P12格式的证书不能直接使用,这里需要通过openssl工具转换为.pem格式。

先通过以下命令进行转换,得到pem格式的证书

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12

再转换得到pem格式的key

openssl pkcs12 -nocerts -out key.pem -in key.p12

这一步openssl进行转换时会需要我们输入一个密码用来保护导出的key.pem文件,随便输入即可(需要记住)。

通过以下命令去掉刚才key.pem中的密码

openssl rsa -in key.pem -out key-noenc.pem

到这一步,就得到了pem格式的证书cert.ptm和私钥key-noenc.pem

实际使用中,需要合并证书和私钥到一个文件中。

cat cert.pem key-noenc.pem > cert-key.pem

测试证书

通过openssl工具来测试,是否能通过证书建立到Apple服务器的TLS连接

openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert cert.pem -key key-noenc.pem

如果可以连接成功,就表示pem格式的证书已经制作好了

发送推送

APNs的接口接收特定的数据帧格式,每一条要推送的消息都封装成一个数据帧进行传送。格式如下图:

image

  • Identifier 一个任意的值,用于一条消息的识别
  • Expiry 离线消息超时的时间,如果为0或者小于0,APNs 不会保存这条消息
  • payload 要推送的消息,json格式

payload格式如下:


{
    "aps" : {
        "alert" : "You got your emails.",
        "badge" : 9,
        "sound" : "bingbong.aiff"
    },
    "app_info" : "XXX",
}

其中aps键对应的字典指定了iOS系统展现推送的一些必要信息,如文字,小红点,声音等。另外还可以自定义一些信息一起推送给自己的APP。

复用长连接

为了性能考虑,我们的服务器需要和APNs建立一个长连接,然后当有消息需要推送时,就通过这个连接发送数据帧给APNs。而不是每推送一条消息的都和APNs建立一次连接。

经过测试,如果共用一个连接,APNs推送的速度在500条每秒的水平,也就是每分钟可推送30000个消息。而如果每次建立连接,TLS连接大概需要3秒来建立,这样每分钟只能推20条左右,差别巨大。

所以复用连接是很有必要的。

错误处理

根据Apple的文档描述,当APNs收到一个错误的数据帧时,就会返回这个数据帧的ID,并断开连接。但是从APNs关闭这个连接到我们的服务探测到连接关闭,中间会有一小段时间,根据网络情况不同,最长可能会有几秒钟。这段时间里面,被写入的数据帧将都不会得到处理。

所以我们需要为维护一个已发送消息的列表,按发送的先后顺序排序。如果收到发送错误应答,根据返回的 ID找到出错的消息。然后重新建立连接,从该消息的下一条重新开始发送。

福利

我实现了一个python的推送模块LightingAPNs,部署在国内服务器上,实测性能超过500条每秒。 有以下优势:

  • 复用长连接进行推送
  • 自动处理错误Token造成的连接断开,并自动重发其他
  • 返回错误Token的列表,便于上层软件处理

后面考虑把模块改成线程安全的,这样就可以多连接并行推送了