這是個有趣的話題!
先來聊幾個小故事。
我在人生第一個IoT專案裡,第一次接觸到MQTT協定。
我很快就理解了這個協定。因為,它和企業開發用的MQ產品實在是太像了。
在我職業生涯早期,是的,20年前,當時做一個銀行的專案,就用過MQ這東西,那個專案使用了IBM MQ。隨著工作經驗的增加,在另外一些專案中,也會接觸使用到MQ產品。
後來,我在做國產中介軟體的公司裡開發中介軟體,當時我們有個團隊,專門在做MQ產品。
所以,當我第一次讀MQTT資料的時候,我心裡立馬浮現出了這樣的看法:這不就是一個輕量級的、給IoT應用使用的MQ嗎。
我和XMPP之間的故事要複雜很多。
那是中國電信的一個專案,電信想在手機裡整合融合通訊軟體。運營商的思路也很直白,微信你不是牛逼嗎,你用網際網路簡訊來代替我的電信網簡訊、用網際網路音視訊來取代我的電信通話網路,奪取我的生意,搶奪我的通道。我們運營商必須反擊,我們必須要有應對的解決方案!
當時中國電信想到的解決方案之一,就是手機內建融合通訊軟體。
簡單點說,運營商每年要送出很多的手機,辦套餐送手機,這是運營商常用的行銷套路。運營商的思路,如果這個送的手機,使用者拿到手裡的時候,它就已經自帶了微信所有的功能了,那為啥還要另外再安裝一個微信呢?
在手機裡預置電信運營商開發的一個軟體,這個軟體具有微信所有的功能,功能被直接緊密整合在手機電話簿裡,用來和聯絡人來發資訊、聊天、語音和視訊了,走網際網路通道。
這個電信運營商想內建在手機裡替代微信的軟體,被叫做融合通訊軟體。
因為要做中國電信的這個專案,我第一次接觸到了XMPP協定。
後面的故事不出所料,融合通訊軟體並未能掀起風浪,微信依然被安裝在每一臺手機上。
是的,我和XMPP的情緣,從那時候開始了。
在這個電信的專案中,我使用了Openfire XMPP Server,這應該是Java界最出名的一個開源XMPP伺服器了。
Openfire作為一個標準的XMPP Server,其實它還挺可以的。在開發專案過程中,我當然會讀它的程式碼,我會時不時發出感慨:老外寫程式碼還真是挺認真的!
老外寫東西,真的是認真!但是Openfire開源XMPP伺服器,它並不能讓我滿意。
融合通訊軟體,當然會有不少需要客製化的功能。但是,看看下面這段摘自Openfire的原始碼,就應該會明白,為啥我並不是那麼的滿意。
以下程式碼,來自Openfire專案src/main/java/org/jivesoftware/openfire/XMPPServer.java檔案。
... ...
private void loadModules() {
// Load boot modules
loadModule(RoutingTableImpl.class.getName());
loadModule(AuditManagerImpl.class.getName());
loadModule(RosterManager.class.getName());
loadModule(PrivateStorage.class.getName());
// Load core modules
loadModule(PresenceManagerImpl.class.getName());
loadModule(SessionManager.class.getName());
loadModule(PacketRouterImpl.class.getName());
loadModule(IQRouter.class.getName());
loadModule(MessageRouter.class.getName());
loadModule(PresenceRouter.class.getName());
loadModule(MulticastRouter.class.getName());
loadModule(PacketTransporterImpl.class.getName());
loadModule(PacketDelivererImpl.class.getName());
loadModule(TransportHandler.class.getName());
loadModule(OfflineMessageStrategy.class.getName());
loadModule(OfflineMessageStore.class.getName());
loadModule(VCardManager.class.getName());
// Load standard modules
loadModule(IQBindHandler.class.getName());
loadModule(IQSessionEstablishmentHandler.class.getName());
loadModule(IQPingHandler.class.getName());
loadModule(IQBlockingHandler.class.getName());
loadModule(IQPrivateHandler.class.getName());
loadModule(IQRegisterHandler.class.getName());
loadModule(IQRosterHandler.class.getName());
loadModule(IQEntityTimeHandler.class.getName());
loadModule(IQvCardHandler.class.getName());
loadModule(IQVersionHandler.class.getName());
loadModule(IQLastActivityHandler.class.getName());
loadModule(PresenceSubscribeHandler.class.getName());
loadModule(PresenceUpdateHandler.class.getName());
loadModule(IQOfflineMessagesHandler.class.getName());
loadModule(IQPEPHandler.class.getName());
loadModule(IQPEPOwnerHandler.class.getName());
loadModule(MulticastDNSService.class.getName());
loadModule(IQSharedGroupHandler.class.getName());
loadModule(AdHocCommandHandler.class.getName());
loadModule(IQPrivacyHandler.class.getName());
loadModule(DefaultFileTransferManager.class.getName());
loadModule(FileTransferProxy.class.getName());
loadModule(MediaProxyService.class.getName());
loadModule(PubSubModule.class.getName());
loadModule(IQDiscoInfoHandler.class.getName());
loadModule(IQDiscoItemsHandler.class.getName());
loadModule(UpdateManager.class.getName());
loadModule(InternalComponentManager.class.getName());
loadModule(MultiUserChatManager.class.getName());
loadModule(IQMessageCarbonsHandler.class.getName());
loadModule(ArchiveManager.class.getName());
loadModule(CertificateStoreManager.class.getName());
loadModule(EntityCapabilitiesManager.class.getName());
loadModule(SoftwareVersionManager.class.getName());
loadModule(SoftwareServerVersionManager.class.getName());
// Load this module always last since we don't want to start listening for clients
// before the rest of the modules have been started
loadModule(ConnectionManagerImpl.class.getName());
loadModule(ClusterMonitor.class.getName());
// Keep a reference to the internal component manager
componentManager = getComponentManager();
}
... ...
伺服器裝載的模組功能(Modules),居然是被寫死寫死的!!!
好吧,我承認,在那個專案中,我改過Openfire的原始碼,為了把專案客製化的功能加到伺服器中去。
幫客戶做專案嘛,能夠達到專案目標,能夠按期交付,把款收回來就可以了啊!你還想幹嘛?
我還想幹嘛,是的,我還想幹嘛?
在那個瘋狂的年月,我想說,這個行業裡,人人都會有個網際網路英雄夢!
我也有夢,我有個社交領域的創業夢,我想去試試做這件事,幹這事需要使用實時通訊技術。
於是,我開始開始讀XMPP規範,我讀RFC3920(XMPP Core)、RFC3921(XMPP IM)、XEP-045(Multi-User Chat)... ...
讀了一些規範之後,我得出這樣的結論,Openfire恐怕達不到我的創業要求,我最好是自己來寫一個XMPP Server。
當然,憑我的技術能力,只要我願意,我可以去改Openfire的程式碼,最終也可以實現創業專案的功能需求。
問題是,在那時,對,這時候說的那時,已經是在10年前。在那時,我還是如此的理想化,如此對未來充滿了憧憬,我覺得自己可以幹翻一切,何況只是需要去實現一個實時通訊協定標準和產品。
我可以搞定,我會做得很好!就這樣定了,自己動手寫一個。
於是,有了Basalt、Chalk、Granite這一系列XMPP基礎架構專案。
2年後,我去嘗試了自己的夢,使用XMPP來做自己的社交App專案。
專案最終失敗了,這些XMPP的程式碼,也暫時被封存在了程式碼倉庫裡。
春去秋來,潮起潮落,時光飛逝。
2018年,我來到了一家做IoT的創業公司做CTO。我是第一次做IoT專案,我認真上網查資料,理解技術,設計系統。
由於是第一次做IoT專案,我非常謹慎的選擇了技術方案。在閘道器到網際網路伺服器之間的網際網路通訊協定選擇上,我選擇了使用MQTT,看上去這是一個大眾流行的,也比較安全的解決方案。
產品做出來了,我們開始做推廣,完善產品,繼續融資,擴大推廣。
其實那次,我們已經有點接近成功了。我們部署了3.5k智慧鎖節點,融了Pre-A輪1600w。機構方說,你們下一階段的工作,就是1w個節點,然後融A輪,公司A輪估值不低於2億,後續融資的事你們不用管,我們會搞定一切,你們就專心努力完成業績要求。
然而,在經歷一系列的決策錯誤後,創業最終失敗了... ...
我又在思索了,在自己做社交創業失敗後,我又呆了兩家創業公司,做技術合夥人。一家做到A輪,一家做到Pre-A輪。然而,這兩家公司最終都失敗了。
因為這兩家創業公司,我放棄了去阿里巴巴的工作機會,也放棄了到一家銀行工作的機會(給的offer總包年薪超100w)。
我在重新思考自己的方向,畢竟年齡也不小了,我應該尋求穩定不是嗎?
我選擇了一家上市公司的中央研究院,他們的專案,又是IoT,做智慧城市裡的智慧路燈杆。
我和IoT之間,情緣未了!
我對MQTT還是非常熟悉的,我用它做過IoT應用,開發過IoT產品。
我對XMPP應該說算是精通了,我寫過XMPP基礎開發庫、XMPP伺服器、實現過XMPP的各種標準,並基於XMPP做了了IoT開發平臺。
我嘗試客觀的來評價這兩種協定,如果拋開我更擅長XMPP,以及Lithosphere基於XMPP技術開發的主觀立場,我如何評價這兩種協定呢?
以下兩種協定的對比,包括對兩種協定的優劣點評價,以及針對協定缺點的解決方案討論,都基於我個人觀點。受個人知識面和經驗的限制,如果有評價不準確,沒找對好的解決方案思路等問題,請網友們給予批評指正。
交流,就是想跟大家交流,分析問題,尋求答案,不在於爭論對錯,而是想找到對兩種協定客觀、正確的理解。
缺少P2P通訊機制
MQTT使用者端並沒有一個全域性的、永續性的唯一Client ID,MQTT使用者端的Client ID在每次連線到Broker時,由使用者端指定或由Broker來自動生成。
那麼,當我們想想做特定使用者端的指令控制型別操作時,就會比較不方便了,因為:
解決方案討論:
比較常見的解決方案是,為每個使用者端,建立一個和使用者端Client ID相關的,私有專用的訊息接收Topic,用於接收傳送給這個使用者端的的訊息。
例如,我們約定,當用戶端程式連線Broker成功後,使用者端程式會立即開始監聽名為Clients/<CLIENT_ID>的Topic,通過往這個專用Topic裡傳送訊息,我們就間接實現了P2P的訊息通訊。
在這個約定下,一個使用者端連線成功後,如果它的Client ID是"1234567890",那麼使用者端連線成功後,這個使用者端就開始監聽名為"Clients/1234567890"的Topic。那麼往這個Topic裡publish的訊息,這個使用者端就可以收到。
需要考慮的一個問題是安全性問題,因為我沒有在MQTT的規範裡,讀到有關於主題訂閱的許可權限制的內容。那麼,一個使用者端在理論上,是可以監聽到其它使用者端的私有專用訊息通道的。也許可以考慮給私有訊息都用私鑰加密,來提供更好的安全性。
沒有應用層協定
MQTT提供了一條通訊層的通道,你可以用這個通道來傳輸任何格式的資料。
這個東西有利有弊吧,有利的地方在於靈活性,開發者可以自由的定義任何格式的應用層協定,不受任何規則限制。不利的地方在於,大部分應用開發者,其實並沒有足夠的知識和經驗,可以設計出足夠好的應用層協定。
實時性差
訊息實時性和資料可靠性,這兩個特性看上去是魚與熊掌,往往需要我們做出選擇與權衡。
在MQTT中,設定了QoS.AT_LEAST_ONCE和QoS.EXACTLY_ONCE的訊息,實時性會比較差。
我們在智慧鎖的專案裡,曾做過以下的測試。
我們保持行動端和伺服器端之間的通訊暢通,斷開伺服器到鎖終端裝置之間的通訊通道。
然後我們從App端傳送開鎖指令給鎖終端,由於通訊線路被斷開,這時鎖是不能被開啟的。
2分鐘之後,我們將和伺服器端和鎖終端裝置之間的通訊線路恢復正常,這時候,鎖被開啟了。
解決方案討論:
提供P2P通訊機制
在XMPP網路中,任何需要連線到XMPP伺服器的節點(Node),無論它是人(User)或者是物(Thing),都需要有一個全域性的XMPP賬號。在XMPP概念裡,這個賬號叫做Jabber ID。
使用聯絡人/物的Jabber ID,就可以給它傳送直接訊息(前提為許可權允許,例如:聯絡人在好友列表中)。
對於遠端控制型別的IoT應用來說,這簡直太方便了。
實時性強
XMPP就是一個實時通訊協定,實時性強是它的協定特徵。
在一些IoT應用場合,我們就是需要更好的訊息實時性。
我一條控制指令發過去,指令正確到達且正確執行了,你就返回正常執行確認;如果執行失敗,你就返回對應錯誤碼;如果不能確認執行結果,請告訴我超時了,讓程式來處理超時邏輯。
無論啥情況,請立馬告訴我,而不是2分鐘後再突然執行指令。Ok,這就是我們需要的實時性。
以下的程式碼來自Lithosphere的sand-demo,它示範了這種實時性需求。
... ...
private static class RemotingActionCallback implements IRemoting.Callback {
... ...
@Override
public void executed(Object xep) {
// Code for action has executed correctly.
activity.runOnUiThread(() -> Toast.makeText(activity,
"Action has executed.",
Toast.LENGTH_LONG).show());
}
@Override
public void occurred(StanzaError error) {
// Code for error occurred.
String errorText = "Action execution error: " +
(error.getText() == null ? error.toString() : error.getText().getText());
remotingErrorOccurred(activity, error, errorText);
}
@Override
public void timeout() {
// Processing logic for timeout.
activity.runOnUiThread(() -> Toast.makeText(activity,
"Action execution timeout.",
Toast.LENGTH_LONG).show());
}
}
協定擴充套件性強,內建應用層通訊協定基礎規則
XMPP和MQTT的設計思路有些區別。
MQTT協定的關注點,在於如何在網路層提供一條可信賴通道。然後吧,你願意在這個通道上做啥就做啥,我協定是不管你任何應用層的事情的。
XMPP的協定特徵之一,是具有很強的可延伸性。XMPP全稱Extensible Message and Presence Protocol。XMPP裡的X,是Extensiable(可延伸)的意思,表達了XMPP協定的這個協定特徵。
XMPP協定被分成兩部分,一部分是核心標準規範,包括XMPP Core(RFC3920)和XMPP IM(RFC3921)。
另一部分是XMPP的擴充套件協定,XEPs(XMPP Extension Protocols)。XEPs協定族裡,包含了大概300多個協定,這些協定覆蓋的內容範圍,簡直包羅萬象。這當然證明了XMPP協定的超強擴充套件性特徵。
XMPP協定族覆蓋的內容很多,既包含了網路層的協定,也包括應用層協定。
例如,User Avatar(XEP-0084)。這個協定當然是一個應用層協定,這個規範定義了在實時通訊系統中,使用者虛擬身份使用的解決方案。
在XMPP的標準規範XMPP Core(RFC3920)中,定義了XMPP協定的一堆基礎規則,包括:
好吧,你當然可以認為,這麼多規則,嚴重限制了開發者的想象力嘛!
我會這樣認為,這是設計出色的應用層通訊協定的一個良好基礎。
基於XML,通訊效率差
這應該是XMPP協定受到最多攻擊的一個問題了。XMPP太重了,XMPP不適合行動網際網路,更不適合物聯網。
解決方案討論:
缺少內建的QoS支援
MQTT是一個輕量級的MQ,QoS基本上是MQ軟體的標配,MQTT也不例外。
XMPP由RFC標準協定加上XEPs組成,在RFC標準協定中,並沒有要求提供QoS支援。
對於資料上報型別的IoT應用,QoS是非常必要的。
解決方案討論:
實現QoS協定
XMPP的XEPs裡,目前還缺少關於QoS的正式標準。
可以看到有一個草稿標準Quality of Service(XEP-xxxx)。需要注意的是,這個標準目前的狀態,還是XEPs的草稿標準(ProtoXEP)狀態,這表示這個規範還未能通過XSF(XMPP Standards Foundation/XMPP標準基金會)的標準審批過程。
我讀了一下這個XMPP的QoS草稿標準,這個規範裡的XMPP QoS實現,看上去和MQTT規範裡要求的QoS實現,幾乎沒有什麼區別。
區別只在於,兩個協定的QoS控制指令的術語叫法不一樣。比如:
雖然這只是一個草稿標準,但是由於這個XMPP標準對應的QoS處理邏輯,看上去和MQTT標準裡的QoS實現邏輯,幾乎沒有什麼區別。理論上,可以認為,實現這個XMPP草稿規範後,就可以為你的XMPP實現提供QoS支援了。
缺少IoT相關規範和開源產品實現
XMPP社群的夥計們,有在積極的探索XMPP在IoT領域的應用,我們可以看到社群在這個方向上的一些工作,比如:
但是目前的狀況看上去,XMPP雖然在實時通訊領域取得了很大的成功,但是在IoT應用領域,XMPP還是小眾技術。
IoT方向上相關的標準規範,都還是草稿狀態,並沒有進入正式的標準化過程。
在開源產品實現上,做了IoT支援的開源產品還比較少,而且實現都還比較不完善。我能看到的,以下XMPP開源產品有提供IoT相關的功能:
實現較為複雜,學習成本高
XMPP和MQTT,在協定的設計思路上,有著比較大的差異。
MQTT:Hi,朋友!我會給你一個速度快、簡單好用、安全可靠的通訊通道。Ok,我的工作做完了!你願意在上面幹嘛就幹嘛,我是不管的,請別再來找我了。
XMPP:我們會提供一個靈活、強大的網路通訊協定,它的靈活性來自高擴充套件性,它的強大來自我們提供了幾百個實時通訊相關標準。我們希望這一把能徹底搞定實時通訊領域的各種問題!
什麼,還缺少IoT相關標準?缺少IoT開源產品實現?目前就是這狀況哦!請你深刻理解XMPP技術,自行定義私有IoT通訊協定來解決問題。可以做到的!你放心,XMPP技術相當靈活!可以做到的!我們對你有信心!
和MQTT的完全自由有所不同,如果你使用XMPP協定,你需要遵循協定標準中的一些規則和要求。由於XMPP協定較MQTT協定複雜,功能更多,正常來說,會需要更多的入門學習時間。
從另外一個角度來看,如果是已有的XMPP協定規範,如果是第三方產品中已經實現了的XMPP擴充套件協定功能呢?那可是現成的東西直接拿來用起,爽不爽?
解決方案討論:
兩個協定都可以用於IoT應用。這兩個協定存在著明顯的差異,不能說哪個是更好的,需要結合應用需求,團隊技術能力和技術積累等各種因素,對技術選型進行綜合考慮。
MQTT協定非常適合用在感測器網路為主,資料上報型別的應用中。MQTT的Public-Subscribe模式,以及QoS支援,似乎就是專門為這型別的應用來設計的。
所有的感測器裝置,在上報資料中,只需要帶上裝置身份標識,帶上資料採集時的時間戳,統一上報到資料處理的Topic通道去。
其它事,讓伺服器去幹。
夠用、簡單好用,很爽!
XMPP的優勢在於它的靈活性以及實時性。
如果你的IoT應用,會有複雜的通訊控制邏輯,以及有很高的實時性通訊需求,值得考慮XMPP。
MQTT最大優勢,應該是它的簡單易用性。能不能不要花那麼多時間來學習通訊技術了,能不能儘快開始編碼開搞應用需求?
MQTT的短板,似乎是它的基礎架構只提供了較單一的通訊機制,當我們碰到一些通訊上的複雜需求時,我們需要做更多工作來解決問題。當然,我們可以:
XMPP的優勢在於它的靈活性。如果我有很複雜的通訊邏輯需要處理;如果我想在通訊層上面提供一個更易用的元件開發模型,XMPP可以幫你實現這些複雜需求。
但是需要考慮問題是,你真正理解和學會XMPP了嗎,你能在自研的或者第三方XMPP基礎件上,自由的開發XMPP擴充套件協定了嗎?
兩者都還不夠好!
標準的MQTT,標準的XMPP,在IoT應用中都還不夠好!不夠用!不能夠覆蓋到IoT裡各種複雜應用需求。我們往往需要在標準協定之上,再做很多的工作來解決問題。
連線萬物,哪有這麼簡單!
都不夠好,不夠用
標準MQTT,標準XMPP,對於連線萬物的IoT來說,都還不夠好!不夠用!
MQTT對於感測器資料上報型別的應用,把資料安全傳輸到伺服器端,然後伺服器端一步去做資料分析,去做巨量資料,搞AI。對於這種型別的應用來說,MQTT夠用的。
對於一些更復雜的IoT通訊需求,MQTT的通訊機制太單一了,不夠用,舉兩例子吧:
標準XMPP,也不夠好!也不夠用!缺少IoT標準,市場上的開源產品普遍缺少IoT功能的支援。
XMPP擴充套件性更好,更容易在協定層去做擴充套件功能
XMPP的協定特徵之一,就是高擴充套件性。如果要選其中一個協定來做協定層的擴充套件,XMPP看上去會方便很多。
基於我自己寫的XMPP開源基礎件,我可以用XMPP做到任何事
這個肯定是最重要的因素了,最終決定了Lithosphere的通訊協定技術選型。
如果我當年不是因為想做社交App創業,去寫了XMPP庫,XMPP伺服器。如果當年因為機緣巧合,我寫了MQTT的開源專案,那我現在會怎麼選擇?
人生沒有如果!
假設一下嘛,哈哈。
我想,我應該會選擇做基於MQTT的IoT解決方案。
人生沒有如果!我現在就是寫了XMPP開源基礎件,我就用這些XMPP基礎件,寫了Lithosphere。
使用XMPP的效果如何?
哈哈,可以看看我寫的Hello,Lithosphere系列教學,你來做評價。
如果是想要評估Lithosphere的功能和技術效果,建議看這兩篇教學。
Hello, LoRa!
這個教學裡覆蓋了IoT協定的整個通訊流程,包括IoT終端裝置整合(MCU板)、IoT LPWAN協定通訊(LoRa通訊協定)、IoT通訊閘道器、IoT通訊伺服器端處理。
Hello, WebRTC!
使用封裝好的WebRTC的Webcam外掛,做一個實時監控攝像頭,這事還挺簡單的。
關於MQTT vs. XMPP的話題,就聊這麼多了!