IM系統-消息流化一些常見問題

  • 2022 年 7 月 25 日
  • 筆記

原創不易,求分享、求一鍵三連

之前說過IM系統的一些優化,但是在網絡上傳輸數據對於數據的流化和反流化也是處理異常情況的重點環節,不處理好可能會出現一些消息發送成功,但是解析失敗的情況,本文就帶大家來一起了解消息流化中經常遇到的問題以及如何規避。

什麼是流化

我們用到的「流化」這個詞和我們常用的「序列化」類似,序列化指將一個對象的實例轉換成二進制串或文本串的格式,以方便本地保存或者異地傳輸。

信息流化的目的也是為了形成二進制串的格式方便網絡傳輸,可以說「流化」就是「序列化」,兩者沒有本質的區別。

在網絡通信之前,先流化需要發送的消息成二進制,進行網絡傳輸,另外節點收到二進制數據之後,進行反流化解析出消息內容來進行通訊。

黏包問題

在進行TCP socket通訊時經常會使用自定義位元組流,原因是因為TCP通訊如同水流一樣,沒有明確的拆分規則,TCP粘包、拆包屬於網絡底層問題,在數據鏈路層、網絡層、傳輸層都可能出現。出現粘包常見的幾點:

  1. 要發送的數據大於TCP發送緩衝區剩餘空間大小,將會發生拆包。
  2. 待發送數據大於MSS(最大報文長度),TCP在傳輸前將進行拆包
  3. 發送的數據小於TCP發送緩衝區的大小,TCP將多次寫入緩衝區的數據一次發送出去,將會發生粘包
  4. 接收數據端的應用層沒有及時讀取接受緩衝區中的數據,將會發生粘包。

以上的數據包發送的不確定性,為了數據能夠正常解析,在業務層面需要將源源不斷的數據流進行拆分或者合併通常用的方法:

  1. 發送數據是先定義本給數據包的長度的包頭,然後發送對應內容,通常自定義位元組流包格式
  2. 發送端將每個數據包封裝為固定長度(不夠的可以通過補0填充)
  3. 可以在數據包之間設置邊界,如添加特殊符號,將不同的數據包拆分開

除了上面說到的問題,數據傳輸中也存在很多不同系統語言和平台的差異的問題,因為在各種基本的數據類型中,將平台上的基礎類型轉化為二進制流,然後將流轉化成接收的機器上的數據類型,如果機器不一樣就會存在位元組排序的差異、數據位元組大小、數據表示和數據對準的方式等問題,經常會遇到的問題:

  1. 流數據在64位操作系統與32位操作系統之間的兼容
  2. 流數據中字符串的編解碼方式不同的影響
  3. 流數據在不同編程語言之間 java 和c 之間的兼容問題;我們分別來看這些常見的問題如何處理才能解決

Little Endian與Big Endian

Little Endian 和Big Endian是表示計算機位元組順序的兩種格式,所謂的位元組順序指的是長度跨度多個位元組的數據的存放形式。Little Endian是把低位元組存放在內存的低位,而Big Endian將低位元組存放在內存的高位。

為什麼會有Little Endian 和Big Endian?要是都統一成一個就好了。

在計算機系統中,我們是以位元組為單位的,每個地址單元都對應着1個位元組,1個位元組為8bit。但是在除了8bit的char之外,還有16bit的short型,32bit的long型等。

另外對於位數大於8位的處理器,例如16位或者32位的處理器,由於寄存器寬度大於1個位元組,那麼必然存在一個如果將多個位元組排列的問題,因此就導致了Little Endian和Big Endian兩種存儲模式。

我們網絡傳輸上,TCP/IP協議規定了必須使用網絡位元組順序(Big Endian模式),而大多數的PC機上採用了Little Endian模式。所以我們就會注意位元組序列兼容的問題 對跨機器通訊有什麼影響呢,最小內存單元8位 一個位元組。

例如:對4位元組的整形數據1247752720 ,其16進制表示0x4a5f3210,低位是0x10, 最高位0x4a,假設存放這個整形的內存單元0x10000100, 終止地址0x10000103,那麼32位的Little Endian系統中,該整形內存存儲如下:

但是32位的Big Endian體系中,卻存放如下:

所以,流數據在不同的平台之間傳輸時,一定要考慮Little Endian和Big Endian的問題,不然傳輸的數據位元組序列就會解析錯誤。

32位與64位機器

操作系統兼容是流數據傳輸中平台無關的另一個重要問題。我們知道在32位和64位機器上,同一類型的變量的位元組長度可能是不一樣的。比如long 型的變量,在32位機器上是4位元組,但在64位機器上卻是8個位元組。

因此,如果一個64位機器上的long型變量流化後,傳輸到32位機器上時,採用long型來接收和解釋,必然失敗,並且可能導致整個流數據的解析失敗。

對該類問題的解決方式,一般採用這樣的方法:無論是32位機器還是64位機器上,我們都統一規定long型的變量只代表4位元組的整形數,無論發送、傳輸、接收都嚴格按照這個標準;在語言之間也存在long型差異,c語言中long是4位元組,java的long型是8位元組。這也需要採用同樣的方式來解決。

UTF8 與UTF16的轉換

這個問題來源於不同的編程語言,其對字符串的編碼的規則不同。

C/C++ 語言,一般採用UTF-8編碼,比如中文採用UTF-8一般1~3個位元組表示一個字符;如java語言,其內部編碼一般時UTF-16;統一以2個位元組表示一個字符;在Win平台下的VC++ 採用的wchar來存在一個字符,其實就是UTF-16; 上面已經說過如果出現多個位元組的數據,就會有Little Endian 和Big Endian的問題。

解決這種問題最好的方式統一編碼 要麼統一使用UTF-8 要麼統一使用UTF-16 另外還有種方式來解決編碼的問題也比較常用,使用BASE64 編解碼;

Base64算法可以解決中文編碼?

Base64是一種二進制到文本的編碼方式。它是一種將byte數組編碼為字符串的方法,而且編碼出的字符串只包含ASCII基礎字符串。基於Base64編碼的文本只包含了64個ASCII碼字符:

  • A-Z 26個
  • a-z 26個
  • 0-9 10個
    • 1個
  • / 1個 正好64 個字符

使用ASCII碼1個碼一個位元組就能表示,就不存在多個位元組情況,所以經過編碼的中文字符串,都是一個單位元組的序列,不存在多個位元組表示一個碼,所以傳輸的過程中就不需要考慮Little Endian 和Big Endian的轉化問題。

總結

無論多複雜的消息類型最終要做消息流化都會落到基本的數據類型:數值型、整型、浮點型、雙浮點型數據與長整形數、字符串型;做好基礎類型佔用位元組統一和大小端處理,解析消息就會更流暢。

好了,今天的分享就到這。如果本文對你有幫助的話,歡迎點贊&評論&在看&分享,這對我非常重要,感謝🙏🏻。

想要更多交流可以加我微信: