0 前言
應用程式之間要想互相通訊,一起配合來實現業務功能,還需傳輸協議支援。
傳輸協議就是應用程式之間對話的語言。設計傳輸協議,並無太多規範和要求,只需通訊雙方的應用程式都能正確處理該協議&&無歧義。
1 斷句
1.1 分隔符
傳輸協議也是種語言,傳輸資料時,首要解決的就是斷句。
對傳輸層,收到的資料是怎樣的?
就是一段段的位元組,但因網路不確定性,你收到的分段並不一定是生產者發出去的分段。
那在協議中也加上“標點符號”不就行?
而且,並不需要像自然語言中那麼多種的標點符號,而只需定義一個分隔符即可。
這辦法的確可行,很多傳輸協議就採用這種方法,比如HTTP 1.0協議,它的分隔符是換行(\r\n)。但這有個問題:自然語言中,標點符號是專用的,它沒有別的含義,和文字天然區分。
但在資料傳輸過程,無論你定義什麼字元作為分隔符,理論上都有可能會在傳輸的資料中出現。
那如何區分“資料內的分隔符”和真正的分隔符?
得在傳送資料階段,加上分隔符前,把資料內的分隔符轉義,收到資料後再轉義回來。
這的確是個麻煩過程,損失了一些效能。
1.2 預置長度
更加實用的方法。
給每句話前面加一個表示這句話長度的數字,收到資料時,按長度讀。
如:03下雨天03留客天02天留03我不留
這裡固定使用2位數字存放長度,每句話最長可支援99個字。接收後的處理就簡單了,先讀取2位數字03,知道接下來3個字是第一句話,那就等這3個字都收到,即可作為第一句話,同理讀第二句話、第三句話。
這很好解決斷句問題,實現比分隔符方法更簡單,效能也更好,是普遍採用的分隔資料的方法。
redis 的 aof 檔案好像就是前置長度哦,經典方案無處不在~
前置長度是不是也有類似問題呢,03也可能是正常文字裡的內容,也是要轉義吧?
你可以想一下,最好自己實現一下接收資料進行解析的程式碼,你就會明白,前置長度無需轉義。因為解析時,可明確知道當前讀到的這個位置應該是長度還是真正資料,它不需要根據資料流中的內容來確定。
2 雙工收發
2.1 單工通訊
任一時刻,資料只能單向傳輸,一個人說時,另一個人只能聽。
HTTP 1.0協議就是這樣,客戶端與服務端建立個連線後,客戶端發個請求,直到服務端返回響應或請求超時,這段時間內,這個連線通道上不能再發其他請求。
這種單工通訊,效率低,很多瀏覽器和APP為解決效能問題,只能同時在服務端和客戶端間建立多條連線。
單工通訊時,一句對一句,請求和響應按序依次收發,有個天然對應關係。就像被女朋友質問時,女朋友問一句,你才敢答一句。這溝通效率有何意義?
2.2 雙工通訊
而TCP連線是全雙工通道,可同時進行資料的雙向收發,互不影響。要提高吞吐量,應用層協議必須支援雙工通訊。
雙工通訊,不管是客戶端還是服務端建立好連線後,雙方都能基於該socket進行收發訊息,而不是伺服器只能accept到message後才能做些處理。
如果說你和你物件有邊聽邊說的本事,換成雙工協議後,基本就是在和女人講道理,你們就會混亂到分不清到底在回答問題or陳述觀點。
併發下,順序也無法保證。實際設計協議時,一般不關心順序,只需確保請求和響應能夠正確對應。
解決對應問題
傳送請求時,給每個請求加個序號:該序號在本次會話內保證唯一,然後在響應中帶上請求的序號,這就能把請求和響應對應上。
加上序號後,即使如搶答一般混亂,也分得清到底在說啥。
你和你物件就能對自己發出去的請求來編號,回覆對方響應的時候,帶上對方請求的編號即可。這就解決了雙工通訊的主要問題。
在一次會話過程中,開頭的先是唯一序列號嗎?然後後面跟資料長度,再是內容嗎?
那接到訊息的一方,如何分辨序列號的長度大小,做到區分序列號和內容前的資料長度資訊?
開頭就肯定是資料長度,序號也是資料的一部分!所以應該在資料長度的後面。
3 總結
設計傳輸協議時,只要雙方應用程式能夠識別傳輸協議,互相交流即可,並沒啥絕對的規範。
首要得解決斷句,有“分隔符”和“前置長度”兩種斷句方案。
使用ID來標識請求與響應對應關係的方法,是比較通用的實現雙工通訊的方法,可有效提升資料傳輸的吞吐量。
解決了斷句,實現了雙工通訊,配合專用序列化方法,即可實現高效能的網路通訊協議,實現高效能的程式間通訊。很多MQ、RPC框架都是用這種方式來實現它們自己的私有應用層傳輸協議。
簡單的高效能通訊程式:你和你物件三組對話,服務端是你物件,客戶端是你自己,讓倆人在客廳碰見一百萬次,記錄下總共耗時。
https://github.com/WangYangA9...
https://sourcegraph.com/githu...