今天研究了下ios的远程推送,网上的相关教程很多,做了一遍下来记录一下遇到的问题和注意事项(转载请注明)
1.证书及乱七八糟的配置
公钥:app id管理那儿的“Development Push SSL Certificate” push证书,我这儿下载下来叫"aps_developer.cer"
私钥:申请证书时候从钥匙串生成的"CertificateSigningRequest.certSigningRequest"文件在"钥匙串->密钥"那儿生成的与之前输入的名字相同的“专用密钥”,可以右键导出为***.p12文件
合成PEM证书
1)转换公钥
openssl x509 -in aps_developer.cer -inform der -out public.pem
2)转换私钥
openssl pkcs12 -nocerts -in MyPushChatKey.p12 -out private.pem
(这时候要输入密码的)
有了这两个pem文件其实就可以测试一下能否联通苹果的服务器了,网上有,就简写了
telnet gateway.sandbox.push.apple.com 2195 (测试是否能连通苹果的推送测试服务器)
Trying 17.172.232.226...
Connected to gateway.sandbox.push-apple.com.akadns.net.
Escape character is '^]'.
要是出现上面的结果就ok了,然后测试刚才的两个pem:
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert public.pem -key private.pem
输完密码之后,要是输出一堆提示信息就算是ok了
3)把两个证书合成(为了服务器用着方便)
cat public.pem private.pem > push_cert.pem
(这里先输入私钥密码,再输入合成之后的新密码,新密码得记住了,之后server用这个pem发推送的时候要用到)
保存好这些文件,证书这就算ok了。
2.在app里获得deviceToken
刚才一步是让app和苹果推送服务器以及server和苹果推送服务器之间用证书建立起了安全的连接通行证,要想真正实现推送还要有一个deviceToken,设备的唯一令牌,在appDelegateDidFinishLunch里面加入代码:
//请求远程推送
UIRemoteNotificationType type = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound |UIRemoteNotificationTypeAlert;
[application registerForRemoteNotificationTypes:type];
这个type是可选的推送的三个属性,这句话一旦运行之后app就会弹出aleat说请求推送通知是否许可,用户选择之后就进入了
- (void)application:didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
- (void)application:didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
两个方法其中一个内,要是成功了就获得了一个deviceToken,不过我试验的时候出现了error:
Error: Error Domain=NSCocoaErrorDomain Code=3000 UserInfo=0x1655c0 "未找到应用程序的“aps-environment”的权利字符串"
排查之后发现问题是本地的provisioning Profiles(我觉得翻译成开发许可证之类的)并不是最新的(配置过push之后的),最简单的解决办法是在xcode的orgnizer里面把之前的证书删了,把苹果账户后台那儿的profile删了重建一次,再从orgnizer那儿refresh下来,之后就解决了。
获得了一个device token
<b5fb5f22 d764c335 aba18eca 0114e8af acb74ff7 4e624dfe 24c9d59f 8fb6a903>
这个关键的东西就拿到了,一个手机对一个app生成的这个deviceToken是唯一的,要想服务器发推送这个token就要上传到服务器上去。
趁着写这个,先写着app接到远程push时候的回调,简单Log一下
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog("remote push:%@", userInfo);
}
3.用python写个简单的测试push server
这里有个open source的封装openssl(s_client)的python包,APNSWrapper,可以方便的使用它给苹果推送服务器发送请求,就可以不care苹果规定的那个严格的发送包的格式了,不过苹果规定推送的数据不能超过256字节,没试过。
http://code.google.com/p/apns-python-wrapper/wiki/APNSWrapperOverview
这个库先别install,直接引用着,后面要改它源码小优化下
按这个lib提供的例子就能挺简单的使用,不过用起来就发现一些问题(也怪我python刚学)
1) 引用,引这个lib的时候,这个库把__init__.py当做整个库的索引了
import sys
sys.path.append("./APNSWrapper")
from __init__ import *
2) deviceToken,这真心蛋疼,我之前2了,一直以为token是带着那个尖括号的一串,其实是里面那8X8的串,而且要把空格去掉
目测这个样子
deviceToken = “b5fb5f22d764c335aba18eca0114e8afacb74ff74e624dfe24c9d59f8fb6a903”
但是这个怎么测试也不对,后发现苹果给的这个token并不是他要我们传过去的,而是要转码一下
import binascii
deviceToken = binascii.unhexlify("b5fb5f22d764c335aba18eca0114e8afacb74ff74e624dfe24c9d59f8fb6a903");
解16进制之后的这个token才是最后我们要传过去进行通讯的token
之后就可以写发送的代码了,测试了一个最简单的推送
#创建通知对象
notification = APNSNotification()
notification.token(deviceToken)
notification.alert("alert")
notification.badge(5)
notification.sound() #创建发送通知的这个wrapper
pem_cert_name = "push_cert.pem"
wrapper = APNSNotificationWrapper(pem_cert_name, True)
wrapper.append(notification)
wrapper.notify()
运行这个python文件之后,会要求输入PEM的密码,在之后等一两秒,刚部署程序的真机就发出了熟悉的提示音,提示内容就是刚才alert的内容了
打开推送进入程序之后,刚才Log的地方打印出了一个推送数据的dict,这就算成功了。
4.额外内容,使用PEM证书时省去一遍遍输入密码以及一次验证多次推送
网上的教程大多就止于上面的内容了,但是真正的推送服务器跑起来之后怎么也不能每个推送验证一遍证书输一次密码吧,起初试了openssl的命令移除pem里面的密码,但貌似不好使,于是就想到了把密码嵌入到openssl的命令行里面(APNSWrapper里面本质上等价于在命令行里输入openssl命令(打开强制命令行属性后)),找了半天发现代码是命令后加上<-pass pass:my_password>,于是乎找这个APNSWrapper的代码,在connection.py中找到了:
def _command(self):
command = "%(executable)s s_client -ssl3 -cert %(cert)s -connect %(host)s:%(port)s % \
{
'executable' : self.executable,
'cert' : self.certificate,
'host' : self.host,
'port' : self.port
}
这就是他内部组合这个command的地方,最简单的方法直接把这个command串后面加上刚才的密码,类似:
def _command(self):
command = "%(executable)s s_client -ssl3 -cert %(cert)s -connect %(host)s:%(port)s -pass pass:MY_PASSWORD" % \
{
'executable' : self.executable,
'cert' : self.certificate,
'host' : self.host,
'port' : self.port
}
ok,再重新运行试试发现木有变化,原因是APNSConnection(APNSConnectionContext):这个类里会如果不设置force_ssl_command会优先使用SSLModuleConnection执行:
try:
if force_ssl_command:
raise ImportError, "There is force_ssl_command forces command line tool" # use ssl library to handle secure connection
import ssl as ssl_module
self.connectionContext = SSLModuleConnection(certificate, ssl_module = ssl_module)
except:
# use command line openssl tool to handle secure connection
if not disable_executable_search:
executable = find_executable(ssl_command)
else:
executable = ssl_command if not executable:
raise APNSNoCommandFound, "SSL Executable [%s] not found in your PATH environment" % str(ssl_command) self.connectionContext = OpenSSLCommandLine(certificate, executable, debug = debug, password = password)
好办,在数值化这个Wrapper的时候把这个参数设上就好了:
#1.cert 2.is_sandbox 3.will_debug 4.force_ssl_command
wrapper = APNSNotificationWrapper('push_cert.pem', True, False, True)
再次运行,发现不用输入密码就ok了。
其实这个方法并不地道,在参考http://www.36coder.com/study/1012.html 的文章之后明白了其实可以只进行一次验证证书之后进行N次发送推送,这也是openssl支持的通信方式,只是这个库没有封装罢了,按照这为仁兄的方法改过之后,这个简单的python push server就可以挺不错的工作了,当然真实的情况下这个deviceToken是和user一一对应的,每次都要提取相应的token而非测试时候的写死,而且真正上线之后的推送服务器地址也是把刚才地址里面的sandbox去掉。
远程推送这块就算ok了,by the way这个推送推过来的速度真心不确定,在单位的时候瞬间收到,回到宿舍的时候就过了半分钟才收到,测试时候还得耐心等等。还有真心希望网上的筒子们不要死转一篇文章,怎么也加上点亲测之后遇到的问题,多一点有价值的内容而不是无限重复的内容。