今天研究了下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這個推送推過來的速度真心不確定,在單位的時候瞬間收到,回到宿舍的時候就過了半分鍾才收到,測試時候還得耐心等等。還有真心希望網上的筒子們不要死轉一篇文章,怎么也加上點親測之后遇到的問題,多一點有價值的內容而不是無限重復的內容。
