IM跨平臺(tái)技術(shù)學(xué)習(xí)(五):融云基于Electron的IM跨平臺(tái)SDK改造實(shí)踐總結(jié)
本文由融云技術(shù)團(tuán)隊(duì)分享,有修訂和改動(dòng)。
1、引言
Electron 憑借其相對(duì)更低的研發(fā)成本投入、強(qiáng)大的跨平臺(tái)支持、擁有基數(shù)龐大的 Javascript 開(kāi)發(fā)者受眾等優(yōu)勢(shì),在 PC 端跨平臺(tái)桌面開(kāi)發(fā)領(lǐng)域異軍突起,大受歡迎。
本文分享的是融云基于Electron的IM跨平臺(tái)PC端SDK改造過(guò)程中所總結(jié)的一些實(shí)踐經(jīng)驗(yàn),希望對(duì)你有用。

* 友情提示:如果您對(duì)Electron的基礎(chǔ)概念還不太了解,建議您先從本系列文章的首篇《快速了解新一代跨平臺(tái)桌面技術(shù)——Electron》和第2篇《Electron初體驗(yàn)(快速開(kāi)始、跨進(jìn)程通信、打包、踩坑等)》開(kāi)始閱讀,否則可能難以理解本文的有關(guān)內(nèi)容。
學(xué)習(xí)交流:
- 移動(dòng)端IM開(kāi)發(fā)入門文章:《新手入門一篇就夠:從零開(kāi)發(fā)移動(dòng)端IM》
- 開(kāi)源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點(diǎn)此)
(本文已同步發(fā)布于:http://www.52im.net/thread-4060-1-1.html)
2、系列文章
本文是系列文章中的第5篇,本系列總目錄如下:
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(一):快速了解新一代跨平臺(tái)桌面技術(shù)——Electron》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(二):Electron初體驗(yàn)(快速開(kāi)始、跨進(jìn)程通信、打包、踩坑等)》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(三):vivo的Electron技術(shù)棧選型、全方位實(shí)踐總結(jié)》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(四):蘑菇街基于Electron開(kāi)發(fā)IM客戶端的技術(shù)實(shí)踐》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(五):融云基于Electron的IM跨平臺(tái)SDK改造實(shí)踐總結(jié)》(* 本文)
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(六):網(wǎng)易云信基于Electron的IM消息全文檢索技術(shù)實(shí)踐》(稍后發(fā)布.. )
3、本次改造的技術(shù)目標(biāo)
針對(duì)本次改造,我們需要達(dá)到以下4個(gè)技術(shù)目標(biāo):
1)需提供與傳統(tǒng)桌面通訊軟件相匹配的能力支持;
2)需實(shí)現(xiàn)瀏覽器與Electron不同運(yùn)行時(shí)代碼的高度復(fù)用;
3)便于開(kāi)發(fā)者構(gòu)建多窗口、多進(jìn)程的復(fù)雜桌面端應(yīng)用;
4)需同步適配同一IM端SDK的多個(gè)版本。
以下,我們將逐條討論這4個(gè)目標(biāo)所有實(shí)現(xiàn)的具體技術(shù)內(nèi)容。
4、技術(shù)目標(biāo)1:需提供與傳統(tǒng)桌面通訊軟件相匹配的能力支持
相較于 B/S 架構(gòu)的 Web 網(wǎng)頁(yè)應(yīng)用,我們期望能夠在 Electron 環(huán)境下向開(kāi)發(fā)者提供更為豐富的本地化能力,以及比?Websocket(或Comet)更高效的Socket實(shí)時(shí)雙工通信通道。

借助這些原本在瀏覽器環(huán)境下不便實(shí)現(xiàn)的技術(shù)能力,來(lái)整體提高用戶對(duì)于桌面端產(chǎn)品的使用體驗(yàn),將 Electron 作為一個(gè) C/S 架構(gòu)軟件運(yùn)行平臺(tái)的潛力發(fā)揮到最大。(白話就是,我們希望借助Electron這個(gè)框架,將原本W(wǎng)eb端的一些雞肋能力,做到像原生富客戶端一樣)
5、技術(shù)目標(biāo)2:瀏覽器與Electron不同運(yùn)行時(shí)代碼的高度復(fù)用
由于 Electron 與標(biāo)準(zhǔn) Web 應(yīng)用擁有幾乎相同的技術(shù)生態(tài),因此多數(shù)產(chǎn)品會(huì)要求前端代碼工程兼顧瀏覽器與 Electron。
也就是說(shuō),一套代碼既要打包為傳統(tǒng)桌面端應(yīng)用(利用Electron),又可發(fā)布為瀏覽器中運(yùn)行的 Web 網(wǎng)頁(yè)應(yīng)用。
基于此,我們提供的 IM SDK 需要在兩種不同的運(yùn)行時(shí)環(huán)境下做到差異最小化,避免開(kāi)發(fā)者編寫冗余的平臺(tái)兼容代碼。(白話就是,盡可能在基于Electron的桌面端和純Web網(wǎng)頁(yè)端之間重用更多的代碼,不然又得多擼一個(gè)全新的Electron端,這得多費(fèi)勁)
6、技術(shù)目標(biāo)3:便于開(kāi)發(fā)者構(gòu)建多窗口、多進(jìn)程的復(fù)雜桌面端應(yīng)用
Electron 通過(guò)對(duì) IPC 能力的封裝為桌面端應(yīng)用開(kāi)發(fā)提供了較完善的跨進(jìn)程通訊方案,借助此能力,開(kāi)發(fā)者構(gòu)建的桌面端應(yīng)用也逐漸趨于復(fù)雜。
比較典型的如桌面端IM產(chǎn)品:通常用一個(gè)獨(dú)立窗口做基礎(chǔ)的 IM 聊天業(yè)務(wù),一個(gè)窗口做歷史聊天記錄查詢業(yè)務(wù)。
當(dāng)有音視頻會(huì)議業(yè)務(wù)場(chǎng)景時(shí),還需要再開(kāi)一個(gè)窗口做會(huì)議業(yè)務(wù)。
甚至有開(kāi)發(fā)者提出了與每個(gè)聊天對(duì)象都保持一個(gè)獨(dú)立聊天窗口的需求(產(chǎn)品形態(tài)如 QQ)。
在這類需求下,長(zhǎng)連接狀態(tài)維持、消息同步變得異常復(fù)雜,原因在于以下3個(gè)方面。
1)若每個(gè)進(jìn)程窗口都維持獨(dú)立長(zhǎng)連接,難免會(huì)出現(xiàn)某一進(jìn)程連接與其他進(jìn)程連接狀態(tài)不同步。且開(kāi)發(fā)者需在各進(jìn)程同時(shí)維護(hù)連接狀態(tài),復(fù)雜度較高。同時(shí)還會(huì)造成服務(wù)的并發(fā)能力下降。
2)若僅有單一主窗口進(jìn)行連接維持,其他窗口通過(guò) IPC 能力將主窗口作為連接代理,則需要在主進(jìn)程、各渲染進(jìn)程中維護(hù)復(fù)雜的跨進(jìn)程通訊業(yè)務(wù)代碼,從而推高項(xiàng)目整體的復(fù)雜度。
3)目前的 Electron 開(kāi)發(fā)者絕大多數(shù)來(lái)自于 Web 開(kāi)發(fā)者,既有編程思維是建立在瀏覽器頁(yè)面內(nèi)單進(jìn)程單線程的應(yīng)用模型下構(gòu)建起來(lái)的,對(duì)于處理此類多進(jìn)程模型的產(chǎn)品開(kāi)發(fā)缺乏相關(guān)的經(jīng)驗(yàn)積累。
為降低類似需求場(chǎng)景的業(yè)務(wù)實(shí)現(xiàn)復(fù)雜度,我們需要在 PaaS 能力層面上解決多進(jìn)程連接共享、多進(jìn)程消息同步問(wèn)題,讓開(kāi)發(fā)者在既有編程思維模式下將每個(gè)業(yè)務(wù)實(shí)現(xiàn)的更為順暢。
7、技術(shù)目標(biāo)4:需同步適配同一IM端SDK的多個(gè)版本
我們的既有Web端 IM SDK 存在一個(gè)端多個(gè)不同版本的情況(主要是為了兼容老用戶,舊版本很難一刀切直接扔掉,只能新老版末同時(shí)并存)。
各版本都有不同數(shù)量的客戶積累,且各版本 API 接口設(shè)計(jì)迥異,跨版本升級(jí)成本較高。
考慮到使用不同版本的客戶未來(lái)將業(yè)務(wù)向 Electron 遷移的可能性,我們期望通過(guò)架構(gòu)設(shè)計(jì)的改進(jìn)來(lái)避免既有客戶做過(guò)多的集成代碼修改,在確保既有客戶不因版本升級(jí)而流失的前提下降低 Web 研發(fā)團(tuán)隊(duì)自身的多版本 SDK 維護(hù)成本。
8、本次改造的落地實(shí)踐
針對(duì)上面章節(jié)中確定的技術(shù)目標(biāo),我們將從以下3個(gè)方向著手落地實(shí)踐:
1)剝離各版本的共同業(yè)務(wù)與對(duì)外差異性 API 定義;
2)Electron 與瀏覽器平臺(tái)下 IM SDK 的區(qū)分;
3)解決多進(jìn)程消息同步、多進(jìn)程連接共享問(wèn)題。
以下,我們將逐條分享這3個(gè)方面的具體實(shí)踐內(nèi)容。
9、落地實(shí)踐1:剝離各版本的共同業(yè)務(wù)與對(duì)外差異性API定義
我們的 IM SDK 各版本分別為不同的代碼倉(cāng)庫(kù)獨(dú)立維護(hù),互無(wú)干系。(白話就是,所有端的IM SDK都是獨(dú)立開(kāi)發(fā),從頭造輪子)
這導(dǎo)致所有的功能(包括即將開(kāi)發(fā)的 Electron 桌面解決方案)都可能要在各個(gè)版本倉(cāng)庫(kù)上單獨(dú)實(shí)現(xiàn),不僅開(kāi)發(fā)成本高,還會(huì)導(dǎo)致實(shí)現(xiàn)質(zhì)量無(wú)法保證、或代碼實(shí)現(xiàn)不統(tǒng)一,同時(shí)也推高了產(chǎn)研后續(xù)流程的測(cè)試、上線等環(huán)節(jié)的成本。

▲ IM SDK 不同版本獨(dú)立維護(hù)
基于前述技術(shù)目標(biāo)4的要求,在既有現(xiàn)狀下繼續(xù)開(kāi)發(fā),就意味著需要在兩個(gè)版本的基礎(chǔ)上做不同實(shí)現(xiàn),既不符合程序員的代碼審美,也影響團(tuán)隊(duì)整體的研發(fā)效率。(白話就是,如果又要從頭造輪子實(shí)在太難受)
為更好地達(dá)成技術(shù)目標(biāo)4,團(tuán)隊(duì)決定優(yōu)先通過(guò)重構(gòu)將既有業(yè)務(wù)分層,即各個(gè)版本所必須的業(yè)務(wù)代碼抽象下沉為 IM Engine 包,并為各個(gè)版本 IM SDK 分別實(shí)現(xiàn)不同的API Layer以便與既有線上版本接口對(duì)齊,這樣既可以降低團(tuán)隊(duì)的研發(fā)成本,也可以滿足既有線上客戶后續(xù)的升級(jí)需求。

▲ 重構(gòu)代碼實(shí)現(xiàn)業(yè)務(wù)分層
完成業(yè)務(wù)分層后,對(duì)于 IM SDK 有依賴的其他產(chǎn)品如 RTC SDK,也都可以擺脫對(duì) IM SDK 接口的依賴而直接調(diào)用 Engine 層接口,業(yè)務(wù)層在拓展 RTC 業(yè)務(wù)時(shí),也就無(wú)需再考慮 IM SDK 的版本問(wèn)題。

▲ 業(yè)務(wù)分層后的結(jié)構(gòu)將保證拓展性
做分層的另一個(gè)考慮還為了達(dá)成技術(shù)目標(biāo)2,將與業(yè)務(wù)層的交互限制在 API 層,在 Engine 中處理 Electron 與瀏覽器兩種運(yùn)行時(shí)下的代碼差異,業(yè)務(wù)層只需關(guān)心 IM SDK 的接口調(diào)用而無(wú)需關(guān)心底層差異,確保業(yè)務(wù)層在兩種運(yùn)行時(shí)下只需要維護(hù)極少甚至無(wú)需維護(hù)兼容代碼,便于業(yè)務(wù)層更專注于業(yè)務(wù)開(kāi)發(fā)。
10、落地實(shí)踐2:Electron 與瀏覽器平臺(tái)下 IM SDK 的區(qū)分
在將 Engine 與業(yè)務(wù)層隔離后(見(jiàn)上一節(jié)),需要考慮 Engine 在不同的運(yùn)行時(shí)下的關(guān)鍵能力差異,并依據(jù)能力差異落實(shí) Engine 的底層設(shè)計(jì)。
Electron 環(huán)境下的連接、消息存儲(chǔ)等能力由 c++ 模塊編寫提供(即后面提到的 CppProto.node):

在瀏覽器與 Electron 平臺(tái)下,從連接管理、到消息收發(fā)等實(shí)現(xiàn)方式迥異,團(tuán)隊(duì)需要對(duì) Engine 包繼續(xù)分層,通過(guò) AEngine 抽象類來(lái)定義 IM Engine 的能力接口,并抽象 APIContext 類用來(lái)管理 AEngine 的能力調(diào)用。
考慮到純 Web 應(yīng)用構(gòu)建尺寸問(wèn)題,Electron 的能力實(shí)現(xiàn)代碼不應(yīng)被打包到標(biāo)準(zhǔn) Web 頁(yè)面內(nèi),因此還需要將 Electron 平臺(tái)下的實(shí)現(xiàn)代碼單獨(dú)抽離出來(lái)作為一個(gè)獨(dú)立包(即ElectronSolution),作為可選模塊由開(kāi)發(fā)者選擇安裝使用。

▲ Electron相關(guān)的代碼抽離為可選模塊
如上圖所示,CppEngine 在 ElectronSolution 包中定義,其需要由開(kāi)發(fā)者在 Electron 應(yīng)用創(chuàng)建 BrowserWindow 實(shí)例時(shí)通過(guò) webPreferences.preload 配置屬性向渲染進(jìn)程窗口預(yù)掛載。
APIContext 在初始化 AEngine 實(shí)例時(shí),優(yōu)先檢測(cè) CppEngine 是否已定義。當(dāng)發(fā)現(xiàn)有 CppEngine 定義時(shí),則初始化 CppEngine 提供更豐富的本地化能力,否則初始化 JSEngine。
就像下面的代碼的展現(xiàn)的邏輯:
const engine: AEngine = typeofCppEngine !== 'undefined'
??? newCppEngine()
??: newJSEngine()
11、落地實(shí)踐3:解決多進(jìn)程消息同步、多進(jìn)程連接共享問(wèn)題
ElectronSolution 包截止目前的設(shè)計(jì)中,所有代碼都運(yùn)行在渲染進(jìn)程內(nèi)。
這意味著每個(gè)進(jìn)程彼此獨(dú)立,都在維護(hù)獨(dú)立的進(jìn)程狀態(tài),無(wú)法滿足目標(biāo) 3 中多進(jìn)程狀態(tài)同步、連接共享的需求。
為了解決該問(wèn)題,需要將?CppProto.node?模塊放到主進(jìn)程,在主進(jìn)程中實(shí)現(xiàn)連接管理、消息收發(fā)等能力,多個(gè)渲染進(jìn)程通過(guò) IPC 通信共享主進(jìn)程狀態(tài)。

▲ 多個(gè)渲染進(jìn)程通過(guò) IPC 通信共享主進(jìn)程狀態(tài)
為了達(dá)成技術(shù)目標(biāo)3的要求,ElectronSolution 需要拆分為兩個(gè)子包,即Main 與 Renderer。
具體就是:
1)Main 包運(yùn)行在主進(jìn)程內(nèi),負(fù)責(zé)維持 CppProto.node 模塊的調(diào)用,實(shí)現(xiàn)底層連接管理、消息管理等功能,同時(shí)通過(guò) Electron 提供的 ipcMain 與各渲染進(jìn)程維持通信;
2)Renderer 包中定義 CppEngine 類,繼承自 Engine 包內(nèi)的 AEngine 抽象類,依然通過(guò) webPreferences.preload 用來(lái)作為主進(jìn)程的代理,通過(guò) ipcRenderer 與主進(jìn)程維持通信。

▲ 拆分為Main與Renderer兩個(gè)子包
修改完成后,ElectronSolution 包的整體結(jié)構(gòu)基本確定。
以下列出 ElectronSolution 包關(guān)鍵目錄結(jié)構(gòu)供參考:
node_modules/@rongcloud/electron-solution
├── index.js
├── main
│?? ├── addon
│?? │?? ├── binding
│?? │?? │?? └── electron-v{electron-version}-{platform}-{arch}.node
│?? │?? └── index.js
│?? ├── dist
│?? │?? └── index.js
│?? ├── index.js
│?? └── package.json
└── renderer
│?? ├── dist
│?? │?? └── index.js
│?? ├── index.js
│?? └── package.json
└── package.json
基于上述架構(gòu)變動(dòng),當(dāng)業(yè)務(wù)層需要在多個(gè)渲染進(jìn)程中實(shí)現(xiàn) IM 能力時(shí),僅需要關(guān)注在各個(gè)進(jìn)程中的 IM SDK 接口調(diào)用,由 ElectronSolution 處理多進(jìn)程之間的狀態(tài)同步問(wèn)題。
當(dāng)開(kāi)發(fā)者期望由既有 Web 業(yè)務(wù)向 Electron 平臺(tái)遷移時(shí),開(kāi)發(fā)者也無(wú)需修改既有的 Web 業(yè)務(wù)代碼,僅需要增量編寫主進(jìn)程代碼相關(guān)功能實(shí)現(xiàn),將 ElectronSolution 安裝并集成到 Electron 桌面端應(yīng)用中即可。
最終,我們形成了以下這樣的IM SDK整體結(jié)構(gòu):

12、未來(lái)的規(guī)劃
除了上述IM相關(guān)業(yè)務(wù),后續(xù)我們還打算在Electron平臺(tái)下提升RTC的場(chǎng)景能力。
目前,Electron 平臺(tái)下由 Chromium 原始提供的 WebRTC 能力對(duì)于開(kāi)發(fā)桌面級(jí)音視頻應(yīng)用軟件來(lái)說(shuō)相對(duì)薄弱,我們有計(jì)劃探索借助 node.js 的拓展能力,提供更為底層的 WebRTC 能力拓展如音效、音質(zhì)、視頻特效等。
13、參考資料
[1]?快速了解新一代跨平臺(tái)桌面技術(shù)——Electron
[2]?Electron初體驗(yàn)(快速開(kāi)始、跨進(jìn)程通信、打包、踩坑等)
[3]?WebSocket從入門到精通,半小時(shí)就夠!
[4]?Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)
[5]?一套海量在線用戶的移動(dòng)端IM架構(gòu)設(shè)計(jì)實(shí)踐分享(含詳細(xì)圖文)
[6]?Web端即時(shí)通訊技術(shù)盤點(diǎn):短輪詢、Comet、Websocket、SSE
[7]?搞懂現(xiàn)代Web端即時(shí)通訊技術(shù)一文就夠:WebSocket、socket.io、SSE
(本文已同步發(fā)布于:http://www.52im.net/thread-4060-1-1.html)