摘要:背景現(xiàn)在寫客戶端或者網(wǎng)頁的時候越來越多的需要與長連接打交道尤其是在這個老板動不動就要搞一個聊天系統(tǒng)的時代后端大哥們于是分分鐘就能造一個基于或者的消息協(xié)議出來但是問題在于每做一個新項目后端大哥們就能造出一個新協(xié)議而且能有各種神奇的限制比如說要
背景
現(xiàn)在寫客戶端或者網(wǎng)頁的時候, 越來越多的需要與長連接打交道, 尤其是在這個老板動不動就要搞一個聊天系統(tǒng)的時代, 后端大哥們于是分分鐘就能造一個基于TCP或者WebSockets的消息協(xié)議出來. 但是問題在于每做一個新項目, 后端大哥們就能造出一個新協(xié)議, 而且能有各種神奇的限制. 比如說要在長連接當中保持一個狀態(tài)機, 發(fā)送某條消息后收到的下一條消息一定是XXX, 或者完全一個JSON就直接丟了出來等等. 雖然都能用, 但是卻需要在各種地方維護著不同的底層通信庫, 沒有章法可依, 所以草擬了這個協(xié)議.
目前最熱門的消息協(xié)議莫過于MQTT和gRPC了, 前者被定義為A lightweight messaging protocol for small sensors and mobile devices, optimized for high-latency or unreliable networks, 即一個為傳感器和移動設備定制的消息協(xié)議. 最大的特點莫過于其固定消息頭只有2字節(jié), 以及QoS服務質(zhì)量控制了. 對于前者, 無可厚非, 任何一個長連接的消息協(xié)議都應該可以做到如此, 甚至更簡單(STMP便是如此), 其次其QoS設計使得通信層面就變得很復雜, 使得其更像一個消息隊列協(xié)議, 而不是簡單的通信協(xié)議. 而gRPC則是一個基于ProtocolBuffers發(fā)展起來的RPC協(xié)議以實現(xiàn). 集成度很高, 底層基于HTTP 2, 所以通用性很好, 如果是做大項目并且團隊有一定的技術/運維積累的話, 是非常推薦的選擇, 但是這和STMP不沖突, STMP面向的是對協(xié)議健壯性要求不高, 只需要一個能用的規(guī)范的企業(yè)/團隊中, 你可以用在Web端, 也可以用在客戶端, 或者智能家居等嵌入式設備中, 反觀gRPC, 則顯得過于龐雜.
簡介協(xié)議取名STMP, 意思是最簡單的消息協(xié)議(The simplest message protocol). 項目托管在GitHub上, 包含了完整的協(xié)議文檔以及相關實現(xiàn), 詳細了解請移步GitHub, 同時歡迎提交PR/Issue, 地址是https://github.com/acrazing/stmp.
簡單來說, STMP有以下特點:
非常精簡的固定頭部, 僅有一字節(jié)(二進制序列化)
支持二進制序列化(TCP)以及文本序列化(WebSockets), 文本序列化支持消息分包傳送(傳遞二進制數(shù)據(jù))
與IP協(xié)議掩碼類似的上層路由控制
負載編碼格式對協(xié)議透明
心跳檢測
四種消息類型: 心跳, 請求, 通知, 回復
與HTTP協(xié)議類似的返回狀態(tài)碼控制
消息字段定義一個全雙工的通信系統(tǒng)中, 雙端需要有效識別對方發(fā)來的消息, 并作出相應的處理, 選擇是否回應等操作, 所以除了實際的負載之外, 還需要若干標志字段. STMP中, 完整的消息字段列表如下, 需要注意的是并不是每條消息都會包含所有的這些字段, 需要根據(jù)網(wǎng)絡環(huán)境以及消息類型確定應該包含的字段列表. 但是如果某條消息包含了以下這些字段中的某一些字段的話,排序順序一定與字段在下面出現(xiàn)的順序相同.
消息類型(KIND): 表示一條消息的類型, 可能的取值有:
0: 心跳消息(Ping Message)
1: 請求消息(Request Message)
2: 通知消息(Notify Message)
3: 回復消息(Response Message)
消息編碼格式(ENCODING): 表示負載的編碼格式, 上層應用/編解碼層收到消息后, 可以通過此字段對負載進行解碼操作, 由于頭部長度限制, 可能的取值范圍為0-7, 已經(jīng)約定的編碼格式如下:
0: 保留格式, 表示不包含負載, 此時消息中一定不存在PS以及PAYLOAD字段
1: Protocol Buffers, 參考 Protocol Buffers
2: JSON, 參考 JSON
3: MessagePack, 參考 MessagePack
4: BSON, 參考 BSON
5: 原始二進制數(shù)據(jù)
消息ID(ID): 消息的臨時ID, 取值范圍為0x0000-0xFFFF, 用于請求與回復消息當中, 請求方應該保證在超時的時限內(nèi)此ID唯一, 回復方在回復時帶上此ID以供發(fā)送方識別
消息請求動作(ACTION): 請求的動作, 用于上層應用進行路由控制, 取值范圍為0x00000000-0xFFFFFFFF, 即32位整型, 上層應用中可以寫成xxx.xxx.xxx.xxx的形式, 與IP類似. 接收方在收到相應的動作后必需能夠正確識別, 并轉(zhuǎn)交給相應的處理器進行處理. 其中0x00-0xFF為保留動作, 用于協(xié)議內(nèi)部使用. 目前已使用的動作有:
0x00: 版本協(xié)商(Check Versions)
狀態(tài)碼(STATUS): 處理結(jié)果狀態(tài)碼, 用在回復消息中, 表明對請求的處理結(jié)果, 取值范圍為0x00-0xFF, 其中0x00-0x7F為保留取值, 含義與ACTION無關, 0x80-0xFF為用戶定義的狀態(tài)值, 含義根據(jù)ACTION不同有可能不同. 目前已定義的狀態(tài)碼有(和HTTP類似, 只不過換了個值而已):
0x00: Ok, 200
0x10: MovedPermanently, 301
0x11: Found, 302
0x12: NotModified, 304
0x20: BadRequest, 400
0x21: Unauthorized, 401
0x22: PaymentRequired, 402
0x23: Forbidden, 403
0x24: NotFound, 404
0x25: RequestTimeout, 408
0x26: RequestEntityTooLarge, 413
0x27: TooManyRequests, 429
0x30: InternalServerError, 500
0x31: NotImplemented, 501
0x32: BadGateway, 502
0x33: ServiceUnavailable, 503
0x34: GatewayTimeout, 504
0x35: VersionNotSupported, 505
負載長度(PS): 表示PAYLOAD的長度, 以字節(jié)為單位, 取值范圍為0x00000000-0xFFFFFFFF, 即負載最大長度為4Gb, 此字段存在與否由網(wǎng)絡環(huán)境與ENCODING決定, 如果ENCODING為0, 或者網(wǎng)絡環(huán)境能夠正確的分包(比如WebSockets環(huán)境), 則一定不存在此字段, 否則一定存在此字段.
負載(PAYLOAD): 實際的負載, 長度由PS或者網(wǎng)絡分包結(jié)果確定, 編碼方式由ENCODING決定, 協(xié)議本身不負責負載的編解碼, 需要交由上層的應用進行解釋.
消息類型如前所述, STMP中消息分類四種類型, 不同的消息類型可能包含的字段及含義有所不同, 詳細如下:
心跳消息雙端為了保證對方連接有效性, 必需定期發(fā)送一個心跳消息給對方, 此消息一定不包含任何除了KIND外的其它任何字段. 同時此消息不需要 回復, 如果一方在約定的時間內(nèi)沒有收到對方發(fā)送的心跳消息, 則表明對方已經(jīng)斷開連接或者出現(xiàn)異常, 應該立即斷開連接.
請求消息此消息表示發(fā)送方請求接收方返回某一個資源, 如果在指定的時間內(nèi)未收到接收方的回復, 則放棄等待, 并向上層應用返回一個STATUS為0x25的回復, 表示請求超時.
此消息一定包含KIND, ENCODING, ID, ACTION字段, 可能包含PS, PAYLOAD字段, 一定不包含STATUS字段.
此消息表示發(fā)送方向接收方發(fā)送一個通知, 接收方無需回復此消息.
此消息一定包含KIND, ENCODING, ACTION字段, 可能包含PS, PAYLOAD字段, 一定不包含ID, STATUS字段.
回復消息此消息表示發(fā)送方向接收方發(fā)送一個回復消息以回復對方曾經(jīng)發(fā)送的某一條請求消息, 此消息的ID為接收方發(fā)送的此條請求消息的ID. 如果上層應用在指定的時間內(nèi)未返回消息, 則向發(fā)送方發(fā)送一個STATUS為0x34的回復消息, 表明上層應用處理超時.
此消息一定包含KIND, ENCODING, ID, STATUS字段, 可能包含PS, PAYLOAD字段, 一定不包含ACTION字段.
消息序列化針對不同的網(wǎng)絡環(huán)境, 協(xié)議制定了兩套不同的序列化方式以應對, 主要原因是瀏覽器環(huán)境中將字符串轉(zhuǎn)換成ArrayBuffer再通過WebSockets發(fā)送性能實在無法直視(實現(xiàn)方式可以參考stmp/impl/js/stmp/text.ts, 主要是將UTF-16編碼和字符串轉(zhuǎn)換成UTF-8的Uint8Array), 同時為了更好的Web端調(diào)試, 所以制定了一套文本序列化方案.
二進制序列化二進制序列化中, 固定頭部占一個字節(jié), 包含KIND以及ENCODING字段, 如果KIND為0, 則ENOCDING也必需為0, 表示一個心跳消息. 完整的結(jié)構(gòu)如下:
| 0 ... 7 | 8 ... 15 | 16 ... 23 | 24 ... 31 | | FixedHeader | ID | ACTION | | ACTION | STATUS | | PS | | PAYLOAD ... |
其中的多字節(jié)字段, 包括ID, ACTION, PS字段, 如果存在的話, 一定以BigEndian的方式傳遞. 此外, 固定頭部如下:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | KIND | ENCODING | 0 | 0 | 0 |
最后三個位為保留位(未用到), 全部置零.
文本就序列化所有的字段通過字符|連接, 即:
KIND(1)|ENCODING(1)|ID?(1-5)|ACTION?(1-10)|STATUS?(1-3)|PS?(1-10)|PAYLOAD?(...)
消息分割, 在使用文本序列化方式傳遞二進制數(shù)據(jù)時, 瀏覽器環(huán)境不能高效的將二者混雜在一起, 所以允許分成兩個包進行傳送, 前者傳遞頭部信息, 后者傳遞實際的二進制PAYLOAD, 此時ENCODING一定不為0, 同時, PAYLOAD在頭部包中不存在. WebSockets自身保證了包的有序性.
對于一個心跳消息, 只有一個KIND字段, 所以其結(jié)果一定為"0".
區(qū)分文本消息與二進制消息這是比較有趣的地方, 文本消息和二進制消息可以通過首字節(jié)完全區(qū)別開來: 對于文本消息, 首字節(jié)為"0", "1", "2", "3"中的一個, 即0x30-0x33, 而對于二進制消息, 要么為0x00(心跳消息), 要么大于或者等于0x40, 因為KIND不為0時其值一定大于0b01000000.
版本協(xié)商協(xié)議版本有兩個字段, 分別為MAJOR和MINOR, 二者取值范圍均為0到15, 即0x0到0xF, 可以序列化為MAJOR.MINOR的形式.
當前協(xié)議版本為0.1.
客戶端在發(fā)起連接成功后, 需要發(fā)送一個ACTION為0x00的消息給服務端, 消息ID必需為0, 負載編碼方式為Raw, 負載為客戶端可接受的版本號
列表. 服務端在收到此消息后, 如果可以處理客戶端發(fā)送過來的版本列表中的某一個, 則回復一個STATUS為Ok的回復消息, 負載為所選擇的協(xié)議版本
號, 如果不能處理, 則返回一個VersionNotSupported錯誤消息, 負載為空, 并且關閉連接.
在二進制消息中, 一個版本號序列化為1字節(jié)長度的信息, 其中前4位為MAJOR, 后4位為MINOR值. 多個版本號直接連接在一起. 在文本消息中, 一個版本號序列化為2字節(jié)長度的信息, 其中前1字節(jié)為MAJOR, 后1字節(jié)為MINOR值, 多個版本號直接相連.
實現(xiàn)目前僅實現(xiàn)了Golang和JS的簡單的消息編解碼部分, 地址在: go版本, js版本, 還有很多工作要做T_T, 如果有人提PR就好了?????.
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84446.html
前言 RxHttp截止本文發(fā)表已經(jīng)推廣了4個禮拜,目前已經(jīng)有了141個star,如下: showImg(https://user-gold-cdn.xitu.io/2019/5/20/16ad5f3b6d10d9be); 其中一文,Android 史上最優(yōu)雅的實現(xiàn)文件上傳、下載及進度的監(jiān)聽更是得到了大神劉皇叔微信公眾號的推送,歡迎讀者關注劉皇叔微信公眾號「劉望舒」,每天都有精彩的文章推送,真的很棒...
摘要:接下來繼續(xù)介紹三種架構(gòu)模式,分別是查詢分離模式微服務模式多級緩存模式。分布式應用程序可以基于實現(xiàn)諸如數(shù)據(jù)發(fā)布訂閱負載均衡命名服務分布式協(xié)調(diào)通知集群管理選舉分布式鎖和分布式隊列等功能。 SpringCloud 分布式配置 SpringCloud 分布式配置 史上最簡單的 SpringCloud 教程 | 第九篇: 服務鏈路追蹤 (Spring Cloud Sleuth) 史上最簡單的 S...
摘要:內(nèi)容主要有四個方面趨勢基礎實踐調(diào)試。一趨勢這一章節(jié)主要介紹近幾年和未來的趨勢,包括兩大瀏覽器和對的態(tài)度,以及淘寶天貓和阿里云的實踐情況。完整性是指為了避免網(wǎng)絡中傳輸?shù)臄?shù)據(jù)被非法篡改,使用算法來保證消息的完整性。 摘要: 本文邀請阿里云CDN HTTPS技術專家金九,分享Tengine的一些HTTPS實踐經(jīng)驗。內(nèi)容主要有四個方面:HTTPS趨勢、HTTPS基礎、HTTPS實踐、HTTPS...
摘要:解決問題即時通信要解決三方面的問題雙全工通信低延時支持跨域各種即時通信技術輪詢客戶端定時向服務器發(fā)送請求,服務器接到請求后馬上返回響應信息并關閉連接。優(yōu)點實現(xiàn)真正的即時通信,而不是偽即時。 解決問題 即時通信要解決三方面的問題: 雙全工通信 低延時 支持跨域 各種即時通信技術 輪詢 客戶端定時向服務器發(fā)送Ajax請求,服務器接到請求后馬上返回響應信息并關閉連接。優(yōu)點:后端程序編寫比...
閱讀 3427·2021-09-22 16:00
閱讀 3474·2021-09-07 10:26
閱讀 3036·2019-08-30 15:55
閱讀 2872·2019-08-30 13:48
閱讀 1379·2019-08-30 12:58
閱讀 2180·2019-08-30 11:15
閱讀 961·2019-08-30 11:08
閱讀 537·2019-08-29 18:41