最近在寫一個即時通訊的項目,有一些心得,寫出來給大家分享指正一下。
簡單描述一下這個項目:
實時查詢車輛運作狀态的項目,走TCP通迅。
接口采用GZIP壓縮。
背景是通過Apache的Mina架構
每隔30秒需要發一個心跳包來維持線上狀态,如果伺服器長時間收不到心跳包,會主動斷開連結。
用戶端發送指令消息均采用Protobuff3.0協定進行封裝。
關于Protobuff3.0不太懂的,可以看一下我上一篇簡書Proto3 語言指南
APP的保活
目前越來越多國産手機Rom大多都有像小米那樣的神隐模式,長連接配接就根本無法持續。
耗電(在android中耗電主要是因為占用cpu時間長和一些感應器的使用,而長連接配接基本上分分秒秒都在跑着兩個線程:一個接收一個發送資料)
TCP粘包問題(就是發送方發送的多個資料包,到接收方後粘連在一起,導緻資料包不能完整的展現發送的資料:可能是發送方的原因,也有可能是接受方的原因。)
解決TCP粘包 自定協定,将資料包分為了封包和解包兩個過程。在發送方發送資料時,對發送的資料進行封包操作。在接收方接收到資料時對接收的資料包需要進行解包操作。 自定協定時,封包就是為發送的資料增加標頭,標頭包含資料的大小的資訊,資料就跟随在標頭之後。當然標頭也可以有其他的資訊,比如一些做校驗的資訊。
針對這個項目,我們來分解需要确定的知識點,如下:
推送就像訂餐一樣,隻要留下你的位址, 送餐員就能如期把飯送到你手裡。但在目前的網絡上,這是辦不到的,因為不是每個人都有一個唯一的位址,伺服器想要給我們推送一條消息, 必須知道我們的位址,但伺服器不知道我們在哪。
關于推送的常用幾種方案:
用戶端定期詢問伺服器有沒有新的消息,這樣伺服器不用管用戶端的位址是什麼,用戶端來問,直接告訴它就行。
這種方案最簡單,對于一些不追求實時性的用戶端來說,很适合,隻需要把時間間隔設定成幾個小時取一次, 就能很友善的解決問題。
但對于即時通訊産品來說, 這種方案完全不能用。 假設即時通訊軟體在網絡暢通的情況下發送的消息要求對方10s内就能收到, 如果用輪詢,那麼用戶端要每隔5s連一次伺服器, 如果在移動端, 手機的電量和流量很快就會被消耗殆盡。
定時廣播機制實作
我們可以設定廣播時間然後再廣播接收器中發送心跳包,這個心跳包我們可以直接發送不适用線程,對于發送心跳來說比較頻繁,使用線程還是會耗電,第二,我們心跳其實不需要一天到晚得發送,我們可以在使用者使用完或者鎖屏後25分鐘就暫停發送,然後再過25分鐘喚醒連接配接看看有沒有消息有就接收,沒有繼續斷開,如果使用者打開應用到停止使用有等待25分鐘斷開然後再連接配接檢視離線消息,這一個循環又能保證新消息的接收又不會一直占用CPU。
長連接配接
這大概是目前情況下最佳的方案了,,用戶端主動和伺服器建立TCP長連接配接之後, 用戶端定期向伺服器發送心跳包,有消息的時候,伺服器直接通過這個已經建立好的TCP連接配接通知用戶端。
下面普及一些概念:
短連接配接:是通訊雙方有資料互動時就建立一個連接配接, 資料發送完成後,則斷開此連接配接
長連接配接:大家建立連接配接之後, 不主動斷開。 雙方互相發送資料,發完了也不主動斷開連接配接, 之後有需要發送的資料就繼續通過這個連接配接發送。TCP連接配接在預設的情況下就是所謂的長連接配接。(也就是說連接配接雙方都不主動關閉連接配接, 這個連接配接就應該一直存在)
但是一些外在的因素也會将長連接配接斷開,例如或者伺服器當機、用戶端網絡異、手機網絡和WIFI網絡切換(IP不一樣了)、DHCP的租期等等。
關于手機網絡和WIFI網絡切換為什麼會導緻長連接配接斷開?
因為平時我們使用的NAT裝置最常見就是路由器。NAT裝置會在IP封包通過裝置時修改源/目的IP位址:它不僅改IP, 還修改TCP和UDP協定的端口号,這樣就能讓内網中的裝置共用同一個外網IP。 舉個例子, NAPT維護一個類似下表的NAT表
NAT裝置會根據NAT表對出去和進來的資料做修改,比如将192.168.0.3:8888發出去的封包改成120.132.92.21:9202,外部就認為他們是在和120.132.92.21:9202通信。 同時NAT裝置會将120.132.92.21:9202收到的封包的IP和端口改成192.168.0.3:8888, 再發給内網的主機, 這樣内部和外部就能雙向通信了,但如果其中192.168.0.3:8888 == 120.132.92.21:9202這一映射因為某些原因被NAT裝置淘汰了, 那麼外部裝置就無法直接與192.168.0.3:8888通信了。
關于DHCP的租期
目前測試發現安卓系統對DHCP的處理有Bug, DHCP租期到了不會主動續約并且會繼續使用過期IP, 這個問題會造成TCP長連接配接偶然的斷連。
TCP是有保活定時器的,預設是用保活定時器來維持長連接配接,保活定時器的周期是兩小時。
那為什麼要有心跳包呢? 1個服務端會對應很多用戶端,如果都用保活定時器心跳,那麼這些用戶端在兩小時内都會維持一個長連接配接,那麼問題來了,很多長連結是可以關閉的,而且伺服器帶寬有限,是以需要心跳包,來及時把不需要維持的連結關閉掉。是以需要手動發心跳。保活定時器的兩小時是在是太長了。
心跳包的時間間隔
發送心跳包勢必要先喚醒裝置, 然後才能發送, 如果喚醒裝置過于頻繁, 或者直接導緻裝置無法休眠, 會大量消耗電量, 而且移動網絡下進行網絡通信, 比在wifi下耗電得多. 是以這個心跳包的時間間隔應該盡量的長, 最理想的情況就是根本沒有NAT逾時, 比如剛才我說的兩台在同一個wifi下的電腦, 完全不需要心跳包. 這也就是網上常說的長連接配接, 慢心跳.
根據網上的一些說法, 中移動2/3G下, NAT逾時時間為5分鐘, 中國電信3G則大于28分鐘, 理想的情況下, 用戶端應當以略小于NAT逾時時間的間隔來發送心跳包。
是以心跳間隔逼近NAT逾時的間隔, 同時自動适應NAT逾時間隔的變化。
如果用戶端心跳間隔是固定的,那麼伺服器在連接配接閑置超過這個時間還沒收到心跳時, 可以認為對方掉線, 關閉連接配接。 如果用戶端心跳會動态改變, 如上節提到的微信心跳方案, 應當設定一個最大值, 超過這個最大值才認為對方掉線。 還有一種情況就是伺服器通過TCP連接配接主動給用戶端發消息出現寫逾時, 可以直接認為對方掉線。
心跳包和輪詢看起來類似, 都是用戶端主動聯系伺服器, 但是差別很大。
輪詢是為了擷取資料, 而心跳是為了保活TCP連接配接.
輪詢得越頻繁, 擷取資料就越及時, 心跳的頻繁與否和資料是否及時沒有直接關系
輪詢比心跳能耗更高, 因為一次輪詢需要經過TCP三向交握, 四次揮手, 單次心跳不需要建立和拆除TCP連接配接.
首先Android手機有兩個處理器, 一個叫Application Processor(AP), 一個叫Baseband Processor(BP). AP是ARM架構的處理器,用于運作Android系統; BP用于運作實時作業系統(RTOS), 通訊協定棧運作于BP的RTOS之上. 非通話時間, BP的能耗基本上在5mA左右,而AP隻要處于非休眠狀态, 能耗至少在50mA以上, 執行圖形運算時會更高. 另外LCD工作時功耗在100mA左右, WIFI也在100mA左右. 一般手機待機時, AP, LCD, WIFI均進入休眠狀态, 這時Android中應用程式的代碼也會停止執行。
Android為了確定應用程式中關鍵代碼的正确執行, 提供了Wake Lock的API, 使得應用程式有權限通過代碼阻止AP進入休眠狀态. 但如果不領會Android設計者的意圖而濫用Wake Lock API, 為了自身程式在背景的正常工作而長時間阻止AP進入休眠狀态, 就會成為待機電池殺手.
完全沒必要擔心AP休眠會導緻收不到消息推送。通訊協定棧運作于BP,一旦收到資料包, BP會将AP喚醒, 喚醒的時間足夠AP執行代碼完成對收到的資料包的處理過程. 其它的如Connectivity事件觸發時AP同樣會被喚醒. 那麼唯一的問題就是程式如何執行向伺服器發送心跳包的邏輯. 你顯然不能靠AP來做心跳計時. Android提供的Alarm Manager就是來解決這個問題的. Alarm應該是BP計時(或其它某個帶石英鐘的晶片,不太确定,但絕對不是AP), 觸發時喚醒AP執行程式代碼. 那麼Wake Lock API有啥用呢? 比如心跳包從請求到應答, 比如斷線重連重新登陸這些關鍵邏輯的執行過程, 就需要Wake Lock來保護. 而一旦一個關鍵邏輯執行成功, 就應該立即釋放掉Wake Lock了. 兩次心跳請求間隔5到10分鐘, 基本不會怎麼耗電. 除非網絡不穩定. 頻繁斷線重連, 那種情況辦法不多.
耗電跟push這塊,還有一個心跳對齊的功能。這也是推薦用開源項目的原因。比如說多個app都內建了個推,這些app會公用同一個心跳包,來節省耗電之類的。
一個是Android端發一個漢字給伺服器, 伺服器filter崩潰, 發超過一個漢字, 用戶端filter崩潰, 寫個IoFilter做一下編解碼就好了. 另外User Guide裡面的代碼也有錯誤. 第二個是IoSessionConfig的寫逾時設定了完全不起作用。
後來又發現用戶端隻要在背景超過一定時間,對socket的寫操作就會變得非常詭異, 表現為socket把資料吞了, 告知應用資料已經被對方接收,但是伺服器什麼都沒收到,而且伺服器發送的消息用戶端也收不到。隻要讓app進到前台,之前消失的資料會一股腦發給伺服器, 用戶端會收到伺服器重傳的消息。
這個bug的臨時解決方案是:用神隐模式裡的自定義配置, 把自己想改的設定好就行。
參考文章
Android微信智能心跳方案
android裝置休眠
Optimizing Downloads for Efficient Network Access
關于socket長連接配接的心跳包
Network address translation
C/C++網絡程式設計中的TCP保活
TCP/IP,http,socket,長連接配接,短連接配接——小結。
Android實作推送方式解決方案
[1] 網絡程式設計基礎資料:
《TCP/IP詳解-第11章·UDP:使用者資料報協定》
《TCP/IP詳解-第17章·TCP:傳輸控制協定》
《TCP/IP詳解-第18章·TCP連接配接的建立與終止》
《TCP/IP詳解-第21章·TCP的逾時與重傳》
《理論經典:TCP協定的3次握手與4次揮手過程詳解》
《理論聯系實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《計算機網絡通訊協定關系圖(中文珍藏版)》
《NAT詳解:基本原理、穿越技術(P2P打洞)、端口老化等》
《UDP中一個包的大小最大能多大?》
《Java新一代網絡程式設計模型AIO原理及Linux系統AIO介紹》
《NIO架構入門(三):iOS與MINA2、Netty4的跨平台UDP雙向通信實戰》
《NIO架構入門(四):Android與MINA2、Netty4的跨平台UDP雙向通信實戰》
更多同類文章 ……
[2] 有關IM/推送的通信格式、協定的選擇:
《為什麼QQ用的是UDP協定而不是TCP協定?》
《移動端即時通訊協定選擇:UDP還是TCP?》
《如何選擇即時通訊應用的資料傳輸格式》
《強列建議将Protobuf作為你的即時通訊應用資料傳輸格式》
《移動端IM開發需要面對的技術問題(含通信協定選擇)》
《簡述移動端IM開發的那些坑:架構設計、通信協定和用戶端》
《理論聯系實際:一套典型的IM通信協定設計詳解》
《58到家實時消息系統的協定設計等技術實踐分享》
[3] 有關IM/推送的心跳保活處理:
《Android程序保活詳解:一篇文章解決你的所有疑問》
《Android端消息推送總結:實作原理、心跳保活、遇到的問題等》
《為何基于TCP協定的移動端IM仍然需要心跳保活機制?》
《微信團隊原創分享:Android版微信背景保活實戰分享(程序保活篇)》
《微信團隊原創分享:Android版微信背景保活實戰分享(網絡保活篇)》
《移動端IM實踐:實作Android版微信的智能心跳機制》
《移動端IM實踐:WhatsApp、Line、微信的心跳政策分析》
[4] 有關WEB端即時通訊開發:
《新手入門貼:史上最全Web端即時通訊技術原理詳解》
《Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE》
《SSE技術詳解:一種全新的HTML5伺服器推送事件技術》
《Comet技術詳解:基于HTTP長連接配接的Web端實時通信技術》
《WebSocket詳解(一):初步認識WebSocket技術》
《socket.io實作消息推送的一點實踐及思路》
[5] 有關IM架構設計:
《淺談IM系統的架構設計》
《一套原創分布式即時通訊(IM)系統理論架構方案》
《從零到卓越:京東客服即時通訊系統的技術架構演進曆程》
《蘑菇街即時通訊/IM伺服器開發之架構選擇》
《騰訊QQ1.4億線上使用者的技術挑戰和架構演進之路PPT》
《微信技術總監談架構:微信之道——大道至簡(演講全文)》
《如何解讀《微信技術總監談架構:微信之道——大道至簡》》
《快速裂變:見證微信強大背景架構從0到1的演進曆程(一)》
《17年的實踐:騰訊海量産品的技術方法論》
[6] 有關IM安全的文章:
《即時通訊安全篇(一):正确地了解和使用Android端加密算法》
《即時通訊安全篇(二):探讨組合加密算法在IM中的應用》
《即時通訊安全篇(三):常用加解密算法與通訊安全講解》
《即時通訊安全篇(四):執行個體分析Android中密鑰寫死的風險》
《傳輸層安全協定SSL/TLS的Java平台實作簡介和Demo示範》
《理論聯系實際:一套典型的IM通信協定設計詳解(含安全層設計)》
《微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解》
《來自阿裡OpenIM:打造安全可靠即時通訊服務的技術實踐分享》
有關實時音視訊開發:
《即時通訊音視訊開發(一):視訊編解碼之理論概述》
《即時通訊音視訊開發(二):視訊編解碼之數字視訊介紹》
《即時通訊音視訊開發(三):視訊編解碼之編碼基礎》
《即時通訊音視訊開發(四):視訊編解碼之預測技術介紹》
《即時通訊音視訊開發(五):認識主流視訊編碼技術H.264》
《即時通訊音視訊開發(六):如何開始音頻編解碼技術的學習》
《即時通訊音視訊開發(七):音頻基礎及編碼原理入門》
《即時通訊音視訊開發(八):常見的實時語音通訊編碼标準》
《即時通訊音視訊開發(九):實時語音通訊的回音及回音消除概述》
《即時通訊音視訊開發(十):實時語音通訊的回音消除技術詳解》
《即時通訊音視訊開發(十一):實時語音通訊丢包補償技術詳解》
《即時通訊音視訊開發(十二):多人實時音視訊聊天架構探讨》
《即時通訊音視訊開發(十三):實時視訊編碼H.264的特點與優勢》
《即時通訊音視訊開發(十四):實時音視訊資料傳輸協定介紹》
《即時通訊音視訊開發(十五):聊聊P2P與實時音視訊的應用情況》
《即時通訊音視訊開發(十六):移動端實時音視訊開發的幾個建議》
《簡述開源實時音視訊技術WebRTC的優缺點》
《良心分享:WebRTC 零基礎開發者教程(中文)》
[8] IM開發綜合文章:
《移動端IM開發需要面對的技術問題》
《開發IM是自己設計協定用位元組流好還是字元流好?》
《請問有人知道語音留言聊天的主流實作方式嗎?》
《IM系統中如何保證消息的可靠投遞(即QoS機制)》
《談談移動端 IM 開發中登入請求的優化》
《完全自已開發的IM該如何設計“失敗重試”機制?》
《微信對網絡影響的技術試驗及分析(論文全文)》
《即時通訊系統的原理、技術和應用(技術論文)》
《開源IM工程“蘑菇街TeamTalk”的現狀:一場有始無終的開源秀》
[9] 開源移動端IM技術架構資料:
《開源移動端IM技術架構MobileIMSDK:快速入門》