轉:https://www.zhihu.com/question/19628406/answer/77205019
一、服務端主動推送消息到客戶端過程
鏈接:https://www.zhihu.com/question/24938934/answer/85359794
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
服務端主動推送到客戶端是怎么一個過程
目前服務端給客戶端推送,普遍做法是客戶端與服務端維持一個長連接,客戶端定時向服務端發送心跳以維持這個長連接。當有新消息過來的時候,服務端查出該消息對應的TCP Channel的ID並找到對應的通道進行消息下發。
這只是最基本的通訊模型,在此之上,有衍生出針對消息的發布/訂閱模型,客戶端可以訂閱某一個Topic,服務端根據Topic找到對應的Channel進行批量的消息下發。所有的客戶端隱式的訂閱的all這個opic,所以『類似中國移動給全網信號內所有手機發消息的模式』亦可以理解『廣播消息』,即給all這個Topic發消息。
在此基礎上,又要幾個開源的協議來幫你定義這個事情,比較有名的如MQTT協議(剛好這幾天我看到MQTT協議的中文翻譯,分享給大家),Github上搜索MQTT可以找到對應的開源的協議實現項目,有興趣可以自行搜索。
現在的APP是如何使用消息推送的
實際上,主流的移動平台都已經有系統級的推送產品,Android上有GCM,iOS上有APNS,WinPhone有MPNS。但因為某些你懂的原因,GCM在國內處於不可用狀態,所以國內的移動應用采用另外一種做法---在后台運行一個Service,維持應用於服務端的TCP長連接,以達到實時消息送達的效果。
但是在移動端如何穩定的維持長連接是一件非常復雜的事情,前面說了,客戶端通過定時發送心跳信號(Heartbeat)以維持與服務端的長連接,但是,如果心跳的頻率太頻繁,移動設備耗電增加,心跳間隔太久又可能使得連接被斷開。並且普遍認為移動設備處於一個多變的網絡環境中,WIFI,2G,4G切換,基站切換都會引起網絡變動,在不同網絡環境下的心跳頻率,與網絡變動的重連動作,都需要大量的數據統計分析總結出來。
這僅僅是客戶端的難題,在如今移動應用動輒成百上千的用戶量的情況下,如何維護如此多的長連接,如果應對大規模的消息下發以及后續針對下發消息的各種統計動作都是技術難點。
再者,現在應用一般都是全平台的,發送一條消息,應該同時發送給Android,iOS, WinPhone,Android端走自建的TCP長連接通道,iOS與WinPhone走自家的系統推送通道。那么意味着你服務端要維護這三套推送系統。
顯然對於小團隊,要獨自建立一套消息推送系統的難度非常大,所以市場上涌現出很多優秀的推送產品,幫開發者聚合這些推送方式,並提供統一的推送接口。國外如 Urban Airship, Parse等, 國內有JPush,百度雲推送,信鴿,LeanCloud等。(比較遺憾的是,非常優秀的Parse已經被Facebook宣布停止開發,並將於1年后關閉)
現在除了體量非常大的公司自建推送系統外,一般普通公司都是使用第三方推送服務,以上所有的第三方推送服務,基礎功能都是免費的,如果有條件的話,建議可以集成多家服務,A/B測試對比下推送效果,本人從事與以上某推送公司,在此就不評價各家產品好壞了。
二、服務器端主動推送消息到客戶端原理
現在手機主流的幾個平台都有自家提供Push的功能,讓應用開發者能夠很方便地把Push能力集成到應用中。
Android 上有 GCM (Google Cloud Messaging)
iOS 上有 APNs(Apple Push Notification service)
Windows Phone 上有 MPNs(Microsoft Push Notification service)。
但是由於Windows Phone的市場占比不高,所以一般也就沒有人會專門做wp系統的推送。至於Android的GCM在國內基本上是不可用的。原因主要有以下兩點:
一、國內大部分Android手機都不帶Google服務,也就用不了GCM,這是主要的問題。
二、在國內Google的服務一般都不太穩定,原因你懂的。
所以現在在做消息推送的時候Android平台采用服務器與設備直接拉一條長連接的方式實現功能,而IOS平台則采用蘋果自己的APNs服務。在分別說這兩個平台推送原理的時候,先回答一下題主關於服務器如何先找到設備、再找到app的問題。每一個設備都有一個自己的設備號,而設備中的app又都有一個唯一的包名。所以服務器只需要找到設備號與包名就可以定位到某個設備的某個應用,而這設備號與包名會一起構成一個標識符,叫做device_token,因此問題就簡化為把device_token與消息內容等信息交給服務器,服務器把內容發到唯一的device_token上。這就好像你在上海要通過順豐寄送一個快件兒給某某小區的某某房間,那么快件兒首先會郵遞到順豐公司在北京的總站點,之后再根據小區的地址投遞/路由到某某房間,這樣一個寄件過程就算完成了。在這里,你要寄送的快件兒就是你要發的“消息”,送達房間相當於最終“接收消息的App”,順豐公司在北京的總站點相當於這里提到的“設備”,送達房間的房間號就相當於這個環節里面提到的“包名”。
首先是IOS平台,IOS的推送是通過蘋果自己的APNs服務進行的,用戶需要將device_token以及消息內容等推送信息交給APNs服務器,剩下的均由蘋果自己來完成。但是如果提供的device_token是失效的(app被卸載、系統版本升級導致device_token變化等情況)那么推送過程就會被中斷,頻繁的斷線重連甚至會被APNs認為是一直DoS攻擊。詳情可以參考 為什么蘋果的推送,兩次推送之間間隔比較久的話,第二次推送會很慢? - 沙漠的回答
Android平台推送原理:
下圖是Android平台消息推送的簡單示意圖。
開發者通過第三方推送服務提供商將信息直接下發給需要的設備,第三方推送服務提供商與設備建立一條長連接通道,並且將消息路由到APP中(圖中的設備1與設備2),對於像設備3這種無網絡連接或是沒有成功建立長連接通道的設備,會在設備3連網且推送消息沒有過期的情況下自動收到由第三方推送服務提供商推送過來的消息,保證消息不會丟失。
iOS的push推送原理:
下邊用兩幅圖來簡要說明其推送原理
首先作為設備標識的device-token是由APNs頒發的,App開發者或者第三方推送平台(圖中的Provider)做的工作是收集這個device-token,APNs的推送是要求基於APNs頒發的device-token來推送的。只有正確的device-token會被APNs接受,如果是一個錯誤的、或者無效的device-token(比如App已經卸載了),APNs就不會接受。
接着開發者使用第三方推送平台(圖中的Provider)在將推送內容與范圍選定之后進行推送,第三方推送平台將信息提交給APNs,剩下的操作全部都由APNs來進行完成,整個過程第三方推送平台就不能控制了。
對於友盟推送有更多想要了解的東西可以關注友盟推送的官網 友盟消息推送|app推送以及論壇 http://bbs.umeng.com/forum-push-1.htmliOS實用技巧 - 私有推送實現方法
在開發企業app的時候,有的時候基於安全性的考慮,不允許設備連接外網。這就出現了一個問題,就是iOS的推送功能沒法工作了,因為iOS的推送功能是固化在系統里,必須連接蘋果的APNS服務器才能工作的,為了能讓這類只能工作在內網里的app也能擁有推送功能,就需要我們自己來實現推送功能了。
自主推送的實現方法是利用iOS的voip類app可以駐留在后台的功能。這類app,系統在設備開機時即被啟動,app可以將自己的一個socket委托給系統,在socket有數據到達時,系統就會喚醒app,給它一段很短的cpu時間來處理數據,再加上UIApplication的keepAliveTimeout handler(最小10分鍾間隔一次),可以每隔一段時間就重新建立一次連接,來達到保持socket長連接的需求。
注:由於voip類app可以使app常駐在后台並維持socket長連接,因此蘋果對這類應用有及其嚴格的審查,所有不是真正的voip的app都會被拒絕!切記!
1.設置應用為voip應用
打開<app>-info.plist文件,加入如下的key
Required background modes,追加App provides Voice over IP services
2.將socket設置成異步模式,並將socket設置成VOIP類的,以便系統能夠托管它。
CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)(mosq->sock), &readStream, NULL); //保證不關閉原來的socket CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); //設置成voip socket CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
3.安裝keepAliveTimeout handler,讓系統每隔一段時間重新建立連接
UIApplication *application = [UIApplication sharedApplication]; [application setKeepAliveTimeout:600 handler:^{ [self reconnect]; }];
我參考了jmsnil的MQTTExample工程,這是一個使用mqtt的例子,我把它做了一些修改,以便能在后台運行接收推送。
原工程地址:
https://github.com/jmesnil/MQTTExample
我修改的示例工程地址:
https://github.com/Guou/Demo-mqtt-push
打開終端,輸入下面的命令即可獲得通知
curl -X PUT --data-binary "1" http://eclipse.ttbridge.com/%2FMQTTExample%2Ftestcnpush
該例子只可以工作在真機上,如果您是iOS7以下,請把application:didFinishLaunchingWithOptions:中本地通知權限請求的代碼刪掉。
--------------------
我感覺全部內網環境做推送消息的想法還是算了吧,太麻煩,直接走短信和郵件通知得了
