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