MQTT-SN協(xié)議亂翻之功能描述
前言
緊接上文,這是第三篇,主要是對(duì)MQTT-SN 1.2協(xié)議進(jìn)行總體性功能描述。
嗯,這一部分可以結(jié)合著MQTT協(xié)議對(duì)比著來看。
網(wǎng)關(guān)的廣播和發(fā)現(xiàn)
網(wǎng)關(guān)只能在成功連接到MQTT Server之后,才能夠周期性的在無線個(gè)人區(qū)域網(wǎng)WPNs內(nèi)對(duì)所有客戶端廣播ADVERTISE消息,便于客戶端被動(dòng)知道網(wǎng)關(guān)的存在。
在同一網(wǎng)絡(luò)下,多個(gè)擁有不同Id的網(wǎng)關(guān)可有同時(shí)運(yùn)行中,但會(huì)由客戶端根據(jù)信號(hào)強(qiáng)弱決定連接具體網(wǎng)關(guān),無論何時(shí)只能連接一個(gè)網(wǎng)關(guān)。
客戶端可維護(hù)一份可用網(wǎng)關(guān)列表(包含網(wǎng)關(guān)地址),在接收到包含有新的網(wǎng)關(guān)id的ADVERTISE和GWINFO消息后,其列表需要添加新的網(wǎng)關(guān)元素進(jìn)去。
ADVERTISE廣播消息包含的下一次廣播間隔時(shí)長(zhǎng)Duration屬性,單位秒,設(shè)為變量T_ADV,應(yīng)該盡可能大與15分鐘(900秒),頻率降低是為了避免低速個(gè)人區(qū)域網(wǎng)絡(luò)的擁塞。
針對(duì)接收ADVERTISE消息頻率,處理能力較強(qiáng)客戶端可以用于監(jiān)督網(wǎng)關(guān)是否可用。eg:客戶端連續(xù)N_ADV次接收不到某個(gè)網(wǎng)關(guān)ADVERTISE廣播消息,可認(rèn)為此網(wǎng)關(guān)經(jīng)死掉不可用并且從已維護(hù)的網(wǎng)關(guān)列表中移除。同樣的,作為備用的網(wǎng)關(guān)認(rèn)為主網(wǎng)關(guān)已掛掉,此時(shí)可處于激活狀態(tài),正常發(fā)揮作用。
網(wǎng)關(guān)發(fā)送廣播消息ADVERTISE的時(shí)間間隔很長(zhǎng),這對(duì)導(dǎo)致新加入的客戶端不利,但客戶端可以直接發(fā)送SEARCHGW廣播消息進(jìn)行查詢網(wǎng)關(guān)。大量的新入設(shè)備會(huì)造成廣播風(fēng)暴造成網(wǎng)絡(luò)擁擠,每一個(gè)新加入的客戶端在發(fā)送SEARCHGW廣播消息之前都需要獲取一個(gè)隨機(jī)的延遲發(fā)送值(0-Tsearchgw),在延遲等待發(fā)送期間若接收到其它客戶端發(fā)送的SEARCHGW廣播消息,會(huì)取消掉自己的SEARCHGW廣播消息發(fā)送,等待網(wǎng)關(guān)GWINFO消息通知。
SEARCHGW消息屬性radius廣播半徑,記為變量Rb,1跳(1 hop)在一般密集部署下的MQTT-SN客戶端基本可用。
網(wǎng)關(guān)接收到SEARCHGW會(huì)即刻回復(fù)包含自身id的GWINFO消息??蛻舳耸盏絊EARCHGW后,若有需要延遲發(fā)送的SEARCHGW會(huì)取消掉,若自身維護(hù)一份多個(gè)可用網(wǎng)關(guān)列表,在等待T_GWINFO時(shí)間內(nèi)沒有收到GWINFO消息,會(huì)從列表中取出一條網(wǎng)關(guān)信息組裝成GWINFO消息并廣播出去。這就要求客戶端已運(yùn)行多時(shí),并且維護(hù)多個(gè)可用網(wǎng)關(guān)列表。
GWINFO和SEARCHGW所包含半徑radius屬性值一致,這就要求底層網(wǎng)絡(luò)在傳輸時(shí)進(jìn)行決定是否需要傳輸?shù)狡渌愋途W(wǎng)絡(luò)中。
若沒有接收到響應(yīng),SEARCHGW消息可能被重新傳輸。兩個(gè)連續(xù)的SEARCHGW消息重傳間隔應(yīng)該呈指數(shù)形式增加,避免太密集傳輸。
客戶端的連接建立
無論是基于哪一種傳輸協(xié)議,TCP or UDP,客戶端都需要建立連接,并且保持心跳,邏輯上和服務(wù)器端保持一條不斷線的雙向通道。下面一張圖,演示了客戶端建立連接的過程,并且設(shè)定客戶端在CONNECT消息中標(biāo)志位字段中遺囑WILL屬性為true,然后就有了遺囑主題/消息的請(qǐng)求過程。
很多情況下,連接CONNECT是不需要遺囑支持的,網(wǎng)關(guān)會(huì)直接返回CONNACK消息,但網(wǎng)關(guān)會(huì)因?yàn)閾砣虿恢С忠恍〤ONNET特性,CONNACK所包含返回代碼字段ReturnCode中包含拒絕代碼,要求客戶端檢查是否連接成功,區(qū)別對(duì)待。比如:
CONNACK消息返回狀態(tài)碼為0x01(Rejected: congestion,因擁塞被拒絕),客戶端需要在T_WAIT時(shí)間間隔后進(jìn)行重試。
回話清理
已經(jīng)連接的客戶端斷線后,若之前在CONNECT中沒有設(shè)置過會(huì)話清理(Clean Session)標(biāo)識(shí),那么之前的訂閱等信息在網(wǎng)關(guān)處將會(huì)持久存在。相比MQTT,MQTT-SN中的“Clean Session”標(biāo)識(shí)被擴(kuò)展到遺囑特性中。在CONNECT消息中,CleanSession和Will組合將會(huì)產(chǎn)生以下效果:
- CleanSession=true, Will=true: 網(wǎng)關(guān)將會(huì)刪除之前對(duì)應(yīng)的所有訂閱和遺囑,新的遺囑主題/消息稍后即將重新處理
- CleanSession=true, Will=false: 網(wǎng)關(guān)將會(huì)刪除之前對(duì)應(yīng)的所有訂閱和遺囑,返回CONNACK消息
- CleanSession=false, Will=true: 網(wǎng)關(guān)將繼續(xù)持有之前對(duì)應(yīng)的所有訂閱,新的遺囑主題/消息稍后即將重新處理
- CleanSession=false, Will=false: 網(wǎng)關(guān)將會(huì)繼續(xù)持有之前對(duì)應(yīng)的所有訂閱和遺囑等數(shù)據(jù),并返回CONNACK消息
更新遺囑流程
- CONNEECTION中標(biāo)志位Will中設(shè)置是否需要更新遺囑主題/消息
- 空WILLTOPIC(兩個(gè)字節(jié))消息將會(huì)促使網(wǎng)關(guān)刪除對(duì)應(yīng)遺囑數(shù)據(jù)
- WILLTOPICUPD/WILLMSGUPD可以更新/修改遺囑主題、遺囑消息
- 空白WILLTOPICUPD(兩個(gè)字節(jié))消息意味著請(qǐng)求網(wǎng)清空對(duì)應(yīng)已有的遺囑數(shù)據(jù)
主題注冊(cè)流程
受限于無線傳感器網(wǎng)絡(luò)的有限帶寬和微小消息負(fù)載,PUBLISH消息中不能夠包含完整的主題名稱topic name。這就需要客戶端和網(wǎng)關(guān)之間通過注冊(cè)流程,獲取主題名稱對(duì)應(yīng)的(16位的自然數(shù))topic id,然后塞入PUBLISH消息的topicId屬性中。
客戶端發(fā)送REGISTER消息,網(wǎng)關(guān)返回REGACK消息,其所包含的ReturenCode屬性決定注冊(cè)成功與否:
- ReturnCode = “accepted”,topicId可以很愉快的使用在稍后的PUBLISH消息中
- ReturnCode = “rejected: congestion”,客戶端需要稍等一段時(shí)間(T_WAIT表示,大于5分鐘)再次重新注冊(cè)
- ReturnCode = “rejected: invalid topic ID/not supported”,客戶端需要稍作調(diào)整,再次重新注冊(cè)
任意時(shí)間,只能執(zhí)行一個(gè)REGISTER消息,有沒有完成注冊(cè)流程,需要等待。
網(wǎng)關(guān)->客戶端方向,網(wǎng)關(guān)發(fā)送REGISTER消息給通知客戶端指定topicId對(duì)應(yīng)某個(gè)主題,以便后面發(fā)送PUBLISH消息使用。若客戶端在訂閱SUBSCRIBE消息時(shí)使用了通配符(#/+),那么與之相匹配的topic name也將被一一通知到。因此不建議使用通配符,較為低效。
客戶端發(fā)布流程
客戶端一旦獲取到topic name對(duì)應(yīng)topic id,就可以直接發(fā)送PUBLISH消息了。這和MQTT協(xié)議相比,PUBLISH消息中Topic Name被替換成Topic Id,除此之外,還要注意ReturnCode:
- ReturnCode = “rejected: congestion”,客戶端需要稍等一段時(shí)間(>5分鐘)后再次重試
- ReturnCode = “rejected: invalid topic ID”,客戶端需要重新注冊(cè)topic name獲取topic id,然后再次重新發(fā)布
QoS 1和 QoS 2在任一時(shí)間,都必須等待已有PUBLISH消息完成,才能進(jìn)行下面的PUBLISH消息發(fā)布流程。
預(yù)定義topic id和兩個(gè)字符的topic name
預(yù)定義的topic id已提前指派好對(duì)應(yīng)的topic name,需要客戶端和網(wǎng)關(guān)在代碼層級(jí)支持,省略了中間注冊(cè)流程,在連接建立之后可以馬上進(jìn)行PUBLISH消息,但這需要在PUBLISH標(biāo)志Flags字段中設(shè)置TopicIdType值為0b01(0b10表示兩個(gè)字節(jié)長(zhǎng)度的短topic name)。雖然可以快速發(fā)送PUBLISH消息,但客戶端想訂閱預(yù)定義的topic id,接收對(duì)應(yīng)的PUBLISH消息,一樣需要發(fā)送SUBSCRIBLE消息請(qǐng)求進(jìn)行訂閱。若亂指定預(yù)定義topic id,會(huì)收到ReturnCode=“Rejection: invalid topic Id”的異常。
預(yù)定義的短topic name只有兩個(gè)字符長(zhǎng)度的字符串(也是兩個(gè)字節(jié)),topic id為兩個(gè)字節(jié)表示的一個(gè)自然數(shù)(0-65535),兩者使用場(chǎng)景一致,都需要在標(biāo)志位Flags設(shè)置TopicIdType具體值,0b01表示預(yù)定義topic id,0b10表示兩個(gè)字節(jié)長(zhǎng)度的短topic name,需要分清。
PUBLISH對(duì)應(yīng)QoS -1值
這對(duì)僅僅支持PUBLISH QoS -1的非常簡(jiǎn)單的客戶端實(shí)現(xiàn)而言,除此之外不支持任何特性。它不關(guān)心連接是否建立,也沒有注冊(cè)、訂閱這一說,按照已經(jīng)固化到代碼中的網(wǎng)關(guān)地址直接發(fā)送PUBLISH消息,不關(guān)心網(wǎng)關(guān)地址是否正確、網(wǎng)關(guān)是否存活、消息是否發(fā)送成功。
下面的PUBLISH屬性值依賴于QoS -1的情況:
- QoS標(biāo)志,被置為0b11
- TopicIdType標(biāo)志,可能是(預(yù)定義topic id)0b01也可能是(短topic name)0b10
- TopicId字段,預(yù)定義topic id或短topic name
- Data字段,需要發(fā)送的數(shù)據(jù),沒啥變化
客戶端的訂閱和退訂
客戶端對(duì)某個(gè)主題感興趣,可以發(fā)起SUBSCRIBLE流程,攜帶上感興趣的主題名(topic id),服務(wù)器一般會(huì)返回包含有指定主題Id(topic id)的SUBACK消息。訂閱失敗,可以從PUBACK的ReturnCode中獲知:
- ReturnCode = “rejected: congestion”,客戶端需要稍等一段時(shí)間T_WAIT(>5分鐘)后再次重試
有一種情況是SUBSCRIBLE訂閱主題包含通配符,網(wǎng)關(guān)的處理就很簡(jiǎn)單,在SUBACK中返回的topic id為0x0000。稍后,網(wǎng)關(guān)向客戶端發(fā)送REGISTER消息走注冊(cè)流程,通知通配符匹配到的主題對(duì)應(yīng)的topic id值。
來自客戶端的SUBSCRIBLE消息一樣支持預(yù)定義topic id,以及短topic name,這和PUBLISH消息差不多。
退訂就很簡(jiǎn)單,客戶端發(fā)送UNSUBSCRIBLE消息,網(wǎng)關(guān)返回UNSUBACK消息。
但同一時(shí)刻,客戶端只允許處理訂閱SUBSCRIBLE或取消訂閱UNSUBSCRIBLE按照串行化順序,下一個(gè)操作依賴于上一個(gè)操作完全成功。
網(wǎng)關(guān)發(fā)布流程
服務(wù)器發(fā)布流程和客戶端類似,在發(fā)布之前需要檢測(cè)其主題是否已經(jīng)向客戶端提前注冊(cè)過,若無需要把主題和指定的topic id放入REGISTER消息中發(fā)送給客戶端進(jìn)行注冊(cè)流程,然后等待客戶端處理結(jié)果REGACK。注冊(cè)通過,然后才能正常發(fā)送PUBLISH消息。
網(wǎng)關(guān)需要確保REGISTER的主題以及PUBLISH消息的內(nèi)容負(fù)載都不能太長(zhǎng)超過當(dāng)前網(wǎng)絡(luò)負(fù)載上限(比如在ZigBee環(huán)境下不能超過60個(gè)字節(jié)),取消注冊(cè)/發(fā)布流程就好了。
網(wǎng)關(guān)發(fā)布PUBLISH消息時(shí),客戶端檢測(cè)到未知的topic id,把拒絕理由封裝到PUBACK后,網(wǎng)關(guān)遇到ReturnCode=“Rejected: invalid Topic ID”非法topic id,需要考慮刪除或重新注冊(cè)。
客戶端或許會(huì)拒絕其注冊(cè),或許會(huì)不允許PUBLISH消息,網(wǎng)關(guān)如上靜默處理就好了,失敗就失敗了,不需要告知?jiǎng)e人。
客戶端發(fā)布流程于此類似,需要在發(fā)布之前進(jìn)行主題注冊(cè)以獲取指定的topic id,提交PUBLISH消息后,同樣需要檢查PUBACK所包含的ReturnCode字段是接受還是拒絕,因網(wǎng)絡(luò)擁塞而產(chǎn)生的拒絕,客戶端需要在T_WAIT時(shí)間后再次重試。
客戶端的發(fā)布必須是串行方式,下一個(gè)需要發(fā)送到PUBLISH消息需要等待上一個(gè)發(fā)送成功被網(wǎng)關(guān)接受之后才能進(jìn)行處理。
心跳保活流程
一般是客戶端->網(wǎng)關(guān),網(wǎng)關(guān)->客戶端也沒有問題。但要求PINGREQ -> PINGRESP 一定要單個(gè)時(shí)針循環(huán),PINGREQ發(fā)送者不能也是PINGRESP的發(fā)送者,那樣不但亂了流程,也浪費(fèi)了網(wǎng)絡(luò)資源。嗯,不允許雙向互發(fā)。
客戶端可基于心跳機(jī)制監(jiān)測(cè)已連接網(wǎng)關(guān)健康與否,連續(xù)多次接收不到來自網(wǎng)關(guān)的PINGRESP消息后,客戶端連接下一個(gè)可替換的網(wǎng)關(guān)。因?yàn)榭蛻舳说倪B接和心跳和其它客戶端狀態(tài)屬性不同步,但這可能會(huì)帶來一個(gè)問題,同一時(shí)間若有大量的客戶端洪水般同時(shí)連接一個(gè)網(wǎng)關(guān),網(wǎng)關(guān)可能毫無征兆的會(huì)被沖垮掉。這就要求網(wǎng)關(guān)要有批量的連接處理能力,并發(fā)特性增強(qiáng)才行。
客戶端斷線流程
客戶端主動(dòng)發(fā)送DISCONNECT消息告知網(wǎng)關(guān)需要斷線之后,若有交換信息的需要可以重新發(fā)起一個(gè)新的會(huì)話連接。DISCONNECT消息之后,網(wǎng)關(guān)不會(huì)清理掉已有訂閱和遺囑數(shù)據(jù),除非在之前的CONNECT消息中已硬性設(shè)置了CleanSession會(huì)話清理標(biāo)識(shí)為true。網(wǎng)關(guān)接收到DISCONNECT消息之后會(huì)返回一個(gè)DISCONNECT消息作為響應(yīng)。
有一種情況是客戶端會(huì)突然接收到來自網(wǎng)關(guān)的DISCONNECT消息,這也許是網(wǎng)關(guān)自身發(fā)生了異常錯(cuò)誤,或網(wǎng)關(guān)無法定位客戶端的消息歸屬(客戶端的消息和客戶端無法關(guān)聯(lián)到一起),此時(shí)客戶端需要發(fā)送CONNECT消息重建與網(wǎng)關(guān)的會(huì)話連接。
客戶端重傳流程
客戶端->網(wǎng)關(guān)的消息都是單路傳播的,這依賴于客戶端所持有的已連接網(wǎng)關(guān)的單播地址。
客戶端發(fā)送一個(gè)消息之后,需要啟動(dòng)一個(gè)重試定時(shí)器Tretry和一個(gè)重試計(jì)數(shù)器Nretry用以監(jiān)督網(wǎng)關(guān)消息響應(yīng)。定時(shí)器會(huì)被客戶端在指定時(shí)間內(nèi)接收到來自網(wǎng)關(guān)的消息后取消掉,若沒有準(zhǔn)時(shí)接收到則會(huì)觸發(fā)定時(shí)器執(zhí)行消息重發(fā)流程,連續(xù)Nretry次重發(fā)后,客戶端會(huì)直接取消掉當(dāng)前流程,判斷當(dāng)前網(wǎng)關(guān)已經(jīng)斷線,需要連接到另外一個(gè)可用的網(wǎng)關(guān)。假如另外的網(wǎng)關(guān)也是連接失敗,會(huì)嘗試重連之前的網(wǎng)關(guān)。
若在休眠狀態(tài)下,一旦超過重試計(jì)數(shù)器值,客戶端直接進(jìn)入休眠狀態(tài)。
客戶端休眠支持策略
這里所說的客戶端指的是依賴電池驅(qū)動(dòng)的電子設(shè)備,你要明白一個(gè)事實(shí),節(jié)省電池資源是多麼的重要,省電就是關(guān)鍵,沒電了就沒得玩了嘛。當(dāng)不處于激活狀態(tài)時(shí)為了省電就得需要進(jìn)入睡眠/休眠狀態(tài),當(dāng)有數(shù)據(jù)需要接收或發(fā)送時(shí)就可以醒過來。網(wǎng)關(guān)嘛需要追蹤設(shè)備的休眠狀態(tài)并且支持緩存需要發(fā)送給休眠設(shè)備的消息,在設(shè)備喚醒時(shí)一一發(fā)送。
下面是客戶端的狀態(tài)轉(zhuǎn)換圖,很清晰描述了各種狀態(tài)之間的交互:
客戶端具有五種狀態(tài):激活(active),休眠(asleep),喚醒(awake),斷線(disconnected),丟失(lost),每次只能是其中一種。
網(wǎng)關(guān)需要監(jiān)督客戶端的狀態(tài),開始于CONNECT消息中存活時(shí)長(zhǎng)字段(keep alive),在大于存活時(shí)長(zhǎng)時(shí)間內(nèi)網(wǎng)關(guān)接收不到來自客戶端消息,網(wǎng)關(guān)認(rèn)為客戶端已經(jīng)處于丟失狀態(tài)(lost),會(huì)激活對(duì)應(yīng)的遺囑特性若存在的話。
客戶端發(fā)送DISCONNECT消息但沒有duration休眠時(shí)長(zhǎng)字段,網(wǎng)關(guān)這將處于沒有時(shí)間監(jiān)督的斷線狀態(tài)。一旦包含duration休眠時(shí)長(zhǎng)字段,表示客戶端需要休眠一段時(shí)間,網(wǎng)關(guān)這客戶端被轉(zhuǎn)換為休眠狀態(tài),休眠時(shí)長(zhǎng)為duration所定義在值。超過此休眠時(shí)長(zhǎng)的一段時(shí)間內(nèi),網(wǎng)關(guān)若接收不到客戶端發(fā)送過來的任何消息,那么客戶端會(huì)被轉(zhuǎn)化為丟失狀態(tài),若已設(shè)置遺囑特性,此時(shí)遺囑特性會(huì)生效。客戶端休眠期間需要被發(fā)送的消息都會(huì)被網(wǎng)關(guān)緩存。
睡眠狀態(tài)下流程圖會(huì)更形象的說明流程:
毫無疑問,網(wǎng)關(guān)可使用一個(gè)休眠定時(shí)器維護(hù)客戶端的休眠狀態(tài)等,休眠定時(shí)器會(huì)被停掉當(dāng)網(wǎng)關(guān)接收到客戶端發(fā)送過的PINGREQ消息,網(wǎng)關(guān)從PINGREQ消息所包含的Client Id檢索是否存在已緩存的PUBLISH消息,若有會(huì)一一按照順序發(fā)送到客戶端。所有對(duì)應(yīng)已緩存消息發(fā)送完畢后,會(huì)隨之發(fā)送一個(gè)PINGRESP消息。若沒有緩存消息,網(wǎng)關(guān)直接返回一個(gè)PINGRESP消息。網(wǎng)關(guān)會(huì)重新啟動(dòng)休眠定時(shí)器,網(wǎng)關(guān)維護(hù)的客戶端狀態(tài)被轉(zhuǎn)換為休眠狀態(tài),客戶端在接收到PINGRESP消息之后,將直接轉(zhuǎn)向休眠狀態(tài),節(jié)省用電。
客戶端在喚醒狀態(tài)下處理消息,遵守“客戶端重傳流程”行為,一旦達(dá)到重試計(jì)數(shù)器限制,將進(jìn)入睡眠狀態(tài)。
客戶端從休眠狀態(tài)轉(zhuǎn)向喚醒狀態(tài)用于檢查網(wǎng)關(guān)是否為其緩存消息時(shí),需要發(fā)送一個(gè)PINGREQ消息到網(wǎng)關(guān);從休眠/喚醒狀態(tài)轉(zhuǎn)換為激活狀態(tài),需要發(fā)送一個(gè)CONNECT消息告知網(wǎng)關(guān);轉(zhuǎn)換為斷線狀態(tài)時(shí)需要發(fā)送兩個(gè)字節(jié)的DISCONNECT(沒有休眠時(shí)長(zhǎng)字段duration)消息;需要重新定義的休眠時(shí)長(zhǎng),發(fā)送一個(gè)DISCONNECT消息(包含新的duration時(shí)長(zhǎng)值)通知網(wǎng)關(guān)即可。
小結(jié)
功能性描述介紹完了,基本上MQTT-SN協(xié)議介紹已接近尾聲,最后面的篇章就是短短的實(shí)現(xiàn)描述了。
posted on 2015-01-09 17:32 nieyong 閱讀(6982) 評(píng)論(1) 編輯 收藏 所屬分類: MQTT