MARS做爲優秀的跨平臺網絡層通訊方案開源1年多了,github上收穫過萬的star,期間較爲穩定更新並不頻繁。基於內核socket MARS針對弱網絡環境下的移動應用作了不少比較實用的優化,詳細的優化點和原理在其開源項目的wiki裏有不少文檔說的比較清楚了Mars wiki。本人恰好參與了多款具備IM功能的應用開發,底層網絡通訊集成了MARS,該底層通信模塊已經穩定服務於Android/Ios/windows平臺上多款產品。網上有關MARS使用的實踐經驗還比較少見,這裏總結一下供你們參考。android
MARS支持長鏈接的同時也支持短連接,短連接主要映射成有限制的http鏈接。短鏈接不是MARS的長處,不在本文涉獵,後面提到的全部鏈接如無特指均爲長鏈接。git
讀完文檔就能把MARS用起來仍是得靠運氣的,索性把代碼走讀了一下,恰好能夠梳理梳理長鏈接的數據流。github
上面數據流展現了client端要發送數據的整個過程和涉及到主要API,以Android API爲例,MARS提供了涉及數據輸出的如下重要APIwindows
//初始化 public static void init(Context _context, Handler _handler) //設置長鏈接server public static void setLonglinkSvrAddr(final String host, final int[] ports) //client發送任務接口 public static native void startTask(final Task task); //server主動推送回調 void onPush(final int cmdid, final byte[] data); //client發送數據的回調 boolean req2Buf(final int taskID, Object userContext, ByteArrayOutputStream reqBuffer, int[] errCode, int channelSelect); //client收到迴應數據的回調 int buf2Resp(final int taskID, Object userContext, final byte[] respBuffer, int[] errCode, int channelSelect); //client發送任務結束的回調 int onTaskEnd(final int taskID, Object userContext, final int errType, final int errCode);
實際過程當中MARS提供的接口就比較複雜了,這邊也放一張總結圖感覺一下。安全
Mars對外提供的消息收發接口是基於task的,要先理解task的概念。Mars經過任務來描述一次數據的發送、應答和最終結束。服務器
數據傳輸過程有許多控制參數,任務的定義就是這些控制參數的集合。網絡
public int taskID; // 任務惟一標識,會自動生成。 public int channelSelect; // 任務走長連仍是短連,或者兩個均可以,可選值見 EShort。ELong EBoth public int cmdID; // 長連的 cgi 命令號,用於標識長連請求的 cgi。長連必填項,至關於短連的 cgi。 public String cgi; // 短連的 URI,短連必填項。 public ArrayList<String> shortLinkHostList; //短連所用 host 或者 ip,若是是走短連的任務,必填項。 //optional public boolean sendOnly; // true 爲不須要等待回包,false 爲須要等待回包。默認值爲 false public boolean needAuthed; // true 爲須要登錄態才能發送的任務,false 爲任何狀態下均可以發送的任務,默認值爲 true。 public boolean limitFlow; // true 在手機網絡狀況下會走流量限制,false 不會。默認值爲 true。大數據包請置爲 false。 public boolean limitFrequency; // true 會走頻率限制,false 不會。默認值爲 true。 頻繁發送相同包內容的 Task 請置爲 false。 public int channelStrategy; // channelSelect 爲 EBoth 狀況下,該值爲 ENORMAL 長連存在則走長連,該值爲 EFAST,即便長連存在,可是長鏈接隊列裏有別的任務的時候,會優先走短鏈接。默認值爲 ENORMAL public boolean networkStatusSensitive; // true 沒網絡的狀況下任務會直接返回失敗,不會嘗試去走網絡,false 即便沒網絡,也會嘗試創建鏈接。默認爲 false。 public int priority; // 任務的優先級,可選值見 ETASK_PRIORITY_XX。 public int retryCount = -1; // 任務重試次數,設爲-1,若是任務失敗,會走 Mars 的重試邏。輯,設置大於等於0的數,會以此爲準,默認值-1。 public int serverProcessCost; //該 Task 等待SVR處理的最長時間,也即預計的SVR處理耗時。 public int totalTimeout; // 該 Task 總的超時時間,設置小於等於零的值,會走 Mars 的超時邏輯,不然以此值爲準,默認值爲0。 public Object userContext; // 用戶變量,可填任何值,Mars 不會更改該變量。 public String reportArg; // 統計上報所用,可忽略。
server端配置多個IP,MARS同時發起多個鏈接並取其中最快創建的鏈接使用,其餘釋放掉。該策略確實能提升client創建鏈接的成功率和速度,同時也給server端帶來了併發的壓力,須要根據自身的用戶規模和server資源狀況謹慎使用。咱們開啓了多IP的功能,有幾點值得注意。
MARS提供的接口上定義了幾種不一樣的ip,必定要當心應用。併發
IP | 使用 |
---|---|
Debug IP | 調試IP,線上勿用。 |
NewDns IP | 自開發DNS解析IP。 |
DNS IP | MARS解析出的DNS IP。 |
Backup IP | 保底IP。 |
安全是永恆的話題,長鏈接創建後的第一件事情就是用戶鑑權認證。過程就是client發送一些server端認識的信息來證實本身是合法用戶,能夠繼續通訊。MARS提供了 makesureAuthed/getLongLinkIdentifyCheckBuffer/onLongLinkIdentifyResp 等接口給APP,但該接口是經過回調的方式被動觸發發送鑑權信息的。APP主動發起鑑權信息,也一樣能夠走通用 startTask 接口。app
MARS一直致力於維持鏈接常在,鏈接斷開會自動重連。惋惜沒有提供給APP主動斷開鏈接和重連的API,APP會有場景須要主動斷開當前鏈接,好比上面提到的認證信息更新時或者用戶業務登出時。MARS的 redoTasks會有斷開鏈接的效果,咱們開發APP時就比較討巧的用了這個API來作主動重連的操做。異步
心跳是保持長鏈接的必需手段,MARS也提供了智能心跳的方案。很遺憾咱們的產品是server端主動發心跳包的方案,恰好跟MARS相反的方向。稍稍改造禁用掉MARS的客戶端心跳,走 onPush和 startTask接口一樣能夠實現心跳。
MARS要求實現longlink_packer.cc.rewriteme中定義的函數來達到自定義APP協議的目的。實際產品中server端和client的通訊協議確定須要開發定製的,這部分的實現幾乎是必需的。
能夠根據產品本身的特性定製私有的通信協議,這裏本人給出一個通信協議的例子
struct MessageFormat { uint32_t magicNum; // magically defined num for error message checking uint32_t messageId; // unqiue message identification uint32_t len; // body length char data[]; // body start byte };
這幾乎是最精簡的一個通信協議了,尤爲比較重要的是messageId。messageId對應於MARS的taskId,用於串聯起來IM消息的發送和應答消息對。好比A發送了messageId=1(taskId=1)的「How are you?」到B,B收到後一樣以messageId=1(taskId=1)迴應「I'm fine"。這樣在對A端MARS taskId=1的任務管理全靠這個messageId來標記了。同時有幾點注意事項以下:
我這邊有一個MARS的二次封裝,提供了上面簡單的通信協議同時封裝了Mars task的管理,有興趣的同窗能夠參考一下,文末有連接地址。
MARS xlog經過磁盤文件內存映射的方式得到高效可靠的日誌方案,詳細原理見高性能日誌模塊xlog。實際線上產品使用推薦
MARS有單獨的網絡監控模塊SDT,目前還不能獨立使用。網絡通訊模塊STN裏面也有不少網絡狀況和任務統計的實現,能夠稍微改造一下把這些統計項暴漏給APP層。APP就能夠蒐集統計這些信息彙總到server端,而後運營人員能夠比較輕鬆的瞭解當前全部客戶端的網絡表現啦。
順帶提一下MARS的上報長鏈接狀態的接口 reportConnectInfo 一個小小的提示。該回調函數上報的狀態存在必定的迷惑性。底層網絡長鏈接狀態發生變化時會觸發該狀態上報接口調用,但真正調用到該接口時上報的網絡狀態反應的是當時的鏈接狀態。舉個例子,鏈接斷開觸發上報,上報接口 reportConnectInfo 是在另一個線程裏被調用的,真正調用時狀態可能已經變爲已鏈接了,這樣APP就缺失一個感知鏈接斷開的機會。因此APP不能直接依賴該接口作嚴格的邏輯處理或狀態維護。
總的來講,MARS是一款出色的移動通訊產品網絡層解決方案,若是你須要移動端實時通訊能夠嘗試在產品中集成MARS。若是你以爲接口使用有些複雜,我這邊有一個MARS的二次封裝,你能夠作一個參考或者直接用一下,至少看起來簡單了不少。好比這個C++的例子:
//推送監聽類 class PushHandler :PushListener { virtual void onPush(const std::string &message) { } }; //應答監聽類 class ResponseHandler :ResponseListener { virtual void onResponse(const std::string &message) { printf("response received:%s \n",message.c_str()); } virtual void onError(const int err, const std::string &errMsg) { printf("message send failed:%d \n",err); } virtual void onSuccess() { printf("message send ok \n"); } }; int main(int argc, char* argv[]) { MarsConfig config("39.106.56.27",9001); init(config); PushHandler pushHandler; registerPushListener((PushListener*)&pushHandler); _sleep(2000); ResponseHandler responseHandler; std::string message = "hello"; sendMessage(message.c_str(), message.size(), (ResponseListener*)&responseHandler); _sleep(200000); return 0; }
這個MARS的二次封裝我放在了github上,你們能夠做爲一個瞭解怎樣使用MARS的入口
MarsWrapper