(转) Android平台上关于IM的实践总结
前言
IM通信在互聯(lián)網(wǎng)發(fā)展到現(xiàn)在已經(jīng)是碼農(nóng)的世界里人盡皆知的技術(shù),特別在當(dāng)下移動(dòng)互聯(lián)網(wǎng)迅猛發(fā)展的時(shí)代這種技術(shù)的開發(fā)也更加火熱,其中老牌的代表作就有QQ和MSN,和最近新崛起的微信,默默,易信,來往等眼花繚亂的各種應(yīng)用都把IM技術(shù)應(yīng)用其中。我是Android開發(fā)人員,寫這篇文章主要原因也是因?yàn)槲易约簭氖麻_發(fā)以來主要做過的幾款A(yù)PP都是包含著IM通信,在不斷的摸爬滾打的解決問題的過程中,積累了一些經(jīng)驗(yàn)記錄便將其記錄到博客中作為自己一個(gè)階段性的總結(jié),也可以分享其他需要的開發(fā)者,作為一種參考實(shí)踐的方案,當(dāng)然,我也并非神馬大神級別的人物,如果博客中存在錯(cuò)誤或者讀者看完后覺得有疑問,歡迎在文章下方留下你的建議或問題作為評論。
?
從socket到XMPP協(xié)議
一個(gè)簡單完成的IM通信流程如同下面的模型里面1-2-3-4的步驟所示,兩個(gè)移動(dòng)設(shè)備之間的收發(fā)消息都需要服務(wù)器進(jìn)行中轉(zhuǎn)并做消息的轉(zhuǎn)發(fā)。
整個(gè)IM流程中,服務(wù)器接收消息發(fā)送方的請求,并檢查接收方當(dāng)前是否在線,如果在線的話則直接向接收方發(fā)送消息,如果不在線則作為離線消息存儲(chǔ)到數(shù)據(jù)庫中,等待接收方上線的時(shí)候再將消息進(jìn)行發(fā)送。
IM作為一門網(wǎng)絡(luò)通信的技術(shù),必然涉及到通信協(xié)議的問題,而在Android平臺(tái)上做IM開發(fā),目前我所涉及到的協(xié)議有socket和XMPP兩種類型,當(dāng)然,這兩者并不是一種并列的關(guān)系,從嚴(yán)格意義上來說socket是Java所有網(wǎng)絡(luò)實(shí)現(xiàn)通信的基石,在Java上所有任何網(wǎng)絡(luò)協(xié)議通信,最終都要依賴與socket來實(shí)現(xiàn),在Android平臺(tái)上使用XMPP協(xié)議通信,其最終底層同樣還是依賴于socket實(shí)現(xiàn),最典型的參考例子就是smack了,估計(jì)絕大多數(shù)小企業(yè)在做IM開發(fā)的時(shí)候,都會(huì)使用已經(jīng)把XMPP協(xié)議封裝好smack,以此節(jié)省開發(fā)時(shí)間,smack本身就是用Java語言開發(fā),并采用了mina作為核心實(shí)現(xiàn)的框架(mina本身就是一個(gè)Java的NIO通信框架,但是很久不更新了,原先mina的作者寫了一個(gè)新的NIO通信框架叫做netty,應(yīng)該是做為mina的替代者)。作為socket和XMPP協(xié)議實(shí)現(xiàn)IM通信其主要的優(yōu)缺點(diǎn)如下:
因此,在實(shí)際的IM開發(fā)中,到底是選用XMPP協(xié)議和使用socket來實(shí)現(xiàn),可以根據(jù)開發(fā)應(yīng)用場景和他們的優(yōu)劣來決定,當(dāng)然,也不僅僅局限于IM開發(fā),服務(wù)器給客戶端做消息推送也可以作為參考。
?
長連接和數(shù)據(jù)丟包
IM開發(fā)中除了基本的消息收發(fā),以及協(xié)議使用方式的選擇外,還需要解決所有IM通信技術(shù)所面臨的兩個(gè)共性問題。
其一,保持客戶端和服務(wù)器間的長連接。這是由于業(yè)務(wù)場景所需要, IM通信實(shí)現(xiàn)的基礎(chǔ)是需要時(shí)時(shí)刻刻保持客戶端在服務(wù)器上的在線狀態(tài),這樣才能保證發(fā)送方的消息能夠及時(shí)到接收方,特別是微信的設(shè)計(jì)概念出來后,IM類型的客戶端已經(jīng)沒有在線和離線狀態(tài)的區(qū)別了,這是和QQ最大的區(qū)別 ,在這種情況下對客戶端實(shí)時(shí)在線的要求就變得更高。那么作為客戶端如何實(shí)時(shí)保持在線狀態(tài)呢?由于Java網(wǎng)絡(luò)通信的最底層都要依賴于socket來實(shí)現(xiàn),而socket又分為UDP和TCP兩種類型,從理論上來講,我們使用TCP類型的socket來做實(shí)現(xiàn)通信即可保證長連接的實(shí)現(xiàn),不過這僅僅是理論上而已,為什么單靠TCP不行咧,這跟我們真是的網(wǎng)絡(luò)環(huán)境和操作系統(tǒng)都有一定的關(guān)系,預(yù)知為毛,請接著往下看。
首先,在上一面那種簡單的IM通信圖中,僅僅列出了IM通信的服務(wù)器和客戶端,但是在實(shí)際的網(wǎng)絡(luò)環(huán)境是非常復(fù)雜的(如下圖所示,黃色的閃電圖形代表一個(gè)連接),我們發(fā)送的消息報(bào)文是需要經(jīng)過很多不同的運(yùn)營商線路,在各個(gè)交換機(jī)和路由器間穿梭才能最終到達(dá)接收方的設(shè)備上,下圖是一個(gè)簡單的網(wǎng)絡(luò)消息傳遞圖,黃色的閃電線連起來就是TCP所保持的長連接,理論上這個(gè)鏈接是一直保持通暢的,所以客戶端只需建立連接后就可以撒手不管。
?
但是從性能和耗費(fèi)資源的角度來看,使用socket是非常耗費(fèi)資源的,特別是保持長連接的TCP類型socket,所以在實(shí)際的運(yùn)營商設(shè)備上,這個(gè)長連接保持一小段時(shí)間后就會(huì)被斷開,這樣做也是運(yùn)營商為了節(jié)省和充分利用設(shè)備的資源,而中間有一環(huán)的鏈接斷開之后,雙方就再也無法保持長連接且進(jìn)行通信,因?yàn)槿绻麅H僅依賴于TCP的長連接,此時(shí)服務(wù)器和客戶端是并不知道鏈接已經(jīng)被斷開的,所以就會(huì)造成下面將要說到的數(shù)據(jù)丟包問題。除了運(yùn)營商自己會(huì)將設(shè)備的長連接斷開外,還有包括其他原因也將導(dǎo)致鏈接斷開,例如,運(yùn)營商的設(shè)別斷電或故障等、移動(dòng)設(shè)別接收端本身設(shè)備斷點(diǎn)掉線等諸多復(fù)雜的因素存在導(dǎo)致直接依賴于TCP的長連接方案不可行,其次例如Android操作系統(tǒng)本身也是會(huì)對TCP的長連接做處理,諸如之前所說的,TCP的socket長連接是非常耗費(fèi)資源的,這種資源相對于移動(dòng)設(shè)備來說更加寶貴,所以在一定時(shí)間的TCP長連接閑置后,系統(tǒng)也會(huì)自動(dòng)清除設(shè)備上的長連接,以此節(jié)省移動(dòng)設(shè)別上的能耗。為了徹底解決長連接的問題,IM技術(shù)實(shí)際上引入了心跳包的方案,說起來這種實(shí)現(xiàn)方案也并不復(fù)雜,就是客戶端每個(gè)一會(huì)兒就要向服務(wù)器發(fā)送一個(gè)請求報(bào)文(這個(gè)報(bào)文可以是空的,跟服務(wù)器那邊約定好),目的是為了告訴服務(wù)器“我是在線的”,而服務(wù)器在接收心跳請求報(bào)文之后同樣需要反饋一個(gè)消息告訴客戶端“我知道了”,通過應(yīng)用層上的不斷進(jìn)行消息收發(fā)來維持長連接的存在,當(dāng)客戶端沒有一直沒有收到服務(wù)器的反饋時(shí)默認(rèn)已經(jīng)掉線,進(jìn)行相關(guān)的業(yè)務(wù)邏輯處理后將再重連服務(wù)器,同樣服務(wù)器沒有收到客戶端發(fā)送來的心跳包時(shí),可以默認(rèn)客戶端已經(jīng)掉線,將服務(wù)器上的通信socket進(jìn)行關(guān)閉回收資源。
其二,客戶端發(fā)送數(shù)據(jù)的丟包問題,在第一個(gè)原因中降到運(yùn)營商和各種各樣的網(wǎng)絡(luò)情況都會(huì)導(dǎo)致發(fā)送數(shù)據(jù)的丟包,陷入以下的這種場景A和B之間同樣經(jīng)歷1-2-3-4的一個(gè)消息收發(fā)流程,但是不幸的是由于各種原因,B返回的消息在IM服務(wù)器轉(zhuǎn)發(fā)給A的時(shí)候丟失了,此時(shí)A并不知道B回復(fù)了他消息,而服務(wù)器也不知道A沒有收到B的回復(fù),這種情況下所帶給用戶的體驗(yàn)是及其差勁的。
那么要如何應(yīng)對這種丟包問題呢?目前想到的一種簡單粗暴的可行方案就服務(wù)器每次向客戶端發(fā)送一條消息都要為這條消息做一個(gè)id的標(biāo)記,客戶端收到此消息后需要返回一個(gè)回執(zhí)給服務(wù)器,作為標(biāo)記當(dāng)前客戶端已經(jīng)收到消息的證明,如果服務(wù)器發(fā)送出消息而遲遲沒有收到服務(wù)器的回執(zhí),就把這些消息作為離線消息存放到數(shù)據(jù)庫中,等到客戶端重現(xiàn)上線的時(shí)候再將離線消息推送給客戶端。通過這種方式就可以很好的解決了消息包發(fā)送途中丟包而客戶端和服務(wù)器都不知情的問題。體驗(yàn)一下子就彪上去了,咔咔咔。
?
優(yōu)化性能
由于IM通信的實(shí)時(shí)要求性比較高,伴隨著長連接所耗費(fèi)的資源也相對比較多,所以在移動(dòng)平臺(tái)上的IM通信客戶端想要性能和體驗(yàn)上得去,性能優(yōu)化是必不可少的,對此為Android平臺(tái)上IM優(yōu)化做一些小小的總結(jié),當(dāng)然不一定很全面,如果你有更好的建議歡迎在評論里面做補(bǔ)充哈。
?
?
小結(jié)
以上的內(nèi)容僅僅時(shí)平時(shí)開發(fā)過程中積累的一部分知識和經(jīng)驗(yàn)的總結(jié),希望他能幫助到其他在尋找資料開發(fā)者,同時(shí),也感謝很多開源項(xiàng)目的貢獻(xiàn)者以及熱愛分享的博客撰寫者,因?yàn)橛兴麄兊呢暙I(xiàn)和分享才有這篇文章的誕生,才有我在開發(fā)過程中的進(jìn)步。本文的word文檔也會(huì)同步到我的github倉庫上,歡迎拍磚,歡迎轉(zhuǎn)載,但請注明出處,請勿隨意用于商業(yè)用途。
總結(jié)
以上是生活随笔為你收集整理的(转) Android平台上关于IM的实践总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】windows 7系统安装与配置T
- 下一篇: 【转载】从Docker容器漏洞谈Dock