1. 程式人生 > 實用技巧 >自己動手實現即時通訊協議設計

自己動手實現即時通訊協議設計

文:徐江威

協議選擇

即時訊息有兩個主要特性:時效性和可靠性。

一般的,為了客戶端能及時的接收到訊息,並且控制功耗,長連線是首選的連線方式。同樣的,為了保證訊息的可靠接收,TCP 協議是優先的選擇。

報文格式設計

確定了採用 TCP 長連線協議之後,我們需要為我們的協議設計封包格式。

我們明確三點封包設計原則:

  • 短報文頭
  • 報文負載可被分段
  • 報文資料可被描述

首先,即時通訊資料報文型別大概7到8種即可描述所有主要的通訊業務,因此可以使用短報文頭來攜帶報文分類和索引資訊,而業務層需要的資料由負載攜帶而不由報文頭攜帶,這樣既能為業務層提供足夠的靈活性的同時保證較小的報文結構。

其次,報文負載應該能攜帶多個數據段,每個資料段可以獨立的描述一組資料,從而實現多資料端被封包在一個報文裡。

最後,為了相容不同的作業系統和平臺,在進行資料序列化時,需要對不同的資料型別進行對應的序列化方式,而不同的資料型別在每個平臺上支援程度不一,例如,浮點數的處理,在 JVM 裡的處理和在瀏覽器裡使用 JavaScript 處理就有明顯差異。因此,對資料進行必要的描述以便在不同平臺之間平滑的描述資料。

|--|-00-|-01-|-02-|-03-|-04-|-05-|-06-|-07-|
|--|---------------------------------------|
|01| VER| SN |         RES       |    PN   |
|--|---------------------------------------|
|02|              DATA BEGIN               |
|--|---------------------------------------|
|03|                ... ...                |
|--|---------------------------------------|
|04|               DATA END                |
|--|---------------------------------------|

封包僅8位元組包頭,分別是版本描述、序號、保留位(用於區別不同認證方式)、封包名。抖動、包序等狀態都有由控制器控制,封包不攜帶任何狀態描述。

協議報文

為了實現通訊和應答,我們需要如下協議報文進行資料互動:

  • 握手與驗證
  • 傳輸報文資料
  • 傳輸流資料
  • 心跳

握手協議

握手協議負責驗證接入的客戶機是否能被識別,並讓客戶機提供必要的識別資料。對於無法完成握手流程的客戶機強制關閉連線。

資料包傳輸協議

資料按照打包方式進行傳送和接收,我們可以將需要傳輸的資料封裝為一個數據包,為了便於解包之後還原源資料值和資料屬性,同時考慮到不同平臺的適配,建議將一個屬性值或者一個變數值作為資料包的一個欄位。

通過在包裡封入多個數據值來達到同時傳送若干資料的目的,這樣資料包就需要進行分段規劃,使用分段符來區隔資料欄位。

資料流傳輸協議

資料按照流形式進行傳送和接收,流資料除了在鏈路上傳輸流負載外,還需要對收發兩端進行流控,來保證流的傳輸品質(例如,出現 TCP 擁塞時的流量控制)。這裡需要說明的是為了達到鏈路複用的目的,同時不導致擁塞非流資料包,在同一條傳輸鏈路上,流將被拆解為一個個資料塊,我們將允許各個流的資料塊和資料包在通一條鏈路上進行受控的競爭傳輸。

如上所述,因為流控的引入,我們在 TCP 協議上的流控主要目的是協調兩端的擁塞,我們推薦的流控演算法是基於對端響應時間變化的擁塞控制。通過持續記錄每個數塊到達對端之後,對端給予的應答時間,計算出每個資料塊的潛伏期,跟蹤潛伏期的變化來判斷當前資料傳輸的擁塞,簡單的說就是潛伏期的長短表示了當前的擁塞狀況。

心跳協議

為了保證可靠性和及時性,我們不完全依賴 Socket 的狀態來管理鏈路的連通性。因此,我們需要增加一個心跳協議來主動偵測鏈路的連通性。心跳協議被設計為一個單向協議,對端只進行應答,傳送端根據應答來進行連通性判斷,也就是說,誰需要連通性校驗誰傳送心跳包。客戶端需要判斷連通性,客戶端向伺服器傳送心跳包,傳送間隔和時機由客戶端自行決定,例如,桌面客戶端連線外部電源時可以縮小發送間隔,每3分鐘一次心跳,桌面客戶端使用電池時可以放大發送間隔,每10分鐘一次心跳。同理,伺服器的心跳傳送也由伺服器控制。

資料封包和解包

資料封包就是將資料序列化為二進位制位元組陣列的過程,將資料按照約定的二進位制形式進行序列化。解包的過程就是反序列化的過程,解包時我們需要對接收到的資料的每一個位元組逐一進行讀取,為了提高效率,我們僅進行一次讀取就還原發送的資料格式,充分複用記憶體可以幫助我們提高讀取效率。