應用層協議那些事
應用層的協議簡單來說就是程序之間網路通信的方式,相當於就是我們說話的方式,兩個人能成功的進行溝通需要有兩個先決條件:
1. 大家在說同一種語言
一個只會說漢語的人和一個只會說德語的人註定是無法在一起的;一個胡建人和一個河北人雖然會有較高的誤碼率,但基本還是可以談戀愛的。
2. 需要有「斷句」方式的約定
人類的溝通無一例外是通過「說話的停頓」和「對於語言內容的理解」來進行斷句的:
- 說話的停頓
- 「Hey,你在幹啥呢?」、「寫作業呢」、「吃飯了沒」、」一會兒去吃煎餅「,理解沒有問題
- 對於語言內容的理解
- 「Hey你在幹啥呢吃飯了沒」、」寫作業呢一會兒去吃煎餅「。也能費勁理解(NLP)
3. 對於計算機網路
當兩台計算機需要溝通的時候,情況是十分類似的:
「大家在說同一種語言」很容易理解,我們不能用FTP協議去訪問HTTP的伺服器。
「斷句方式「往往被初學者所忽略,校園招聘的時候經常遇到小朋友做了個「基於socket的文件傳輸工具」,但細問起傳輸協議是怎麼判定文件傳輸結束的時候,有很多人就不明白這個問題的點在哪裡,這時我就十分懷疑這個」傳輸工具「只是個Broken Toy。
由於現在廣泛應用的TCP協議是基於流的傳輸協議(Stream)。也就是說,傳輸層協議本身沒有「斷句」的功能。參見:https://en.wikipedia.org/wiki/Transport_layer#COMPARISON1
這就要求我們在協議層實現」斷句「:
4. 」斷句「的方式
斷句的方式也是隨著計算機網路的發展一直在進化:
- 初,盤古開天闢地,有些類似於PING-PONG協議的非常簡單的定長協議,限定消息長度是4個位元組,協議的功能也十分之弱智,你發PING,我回PONG。
- 1968~1971年誕生的Telnet、FTP這類化石級的協議,一般採用的策略就是和我們寫文章一樣的方式:用回車和換行來標示一條命令的結束。這裡細心的人就會想那遇到消息里本身有
的情況怎麼辦呢?答案就是,轉義!就是在消息本身帶有
的地方前面加一個」」字元。stupid but work - 1982年制定的SMTP協議的Header就沿用了上述的
方式,但SMTP需要傳輸郵件內容,郵件附件。如果繼續採用「轉義」大法,恐怕有點吃不消。這時候就有大神提出了用Base64 Base64 進行編碼,把所有字元都轉化成a-z, A-Z, 0-9等字元。這樣就就完美解決了這個問題。但副作用就是數據會膨脹1/3左右。 - 1991年制定HTTP協議Header繼續沿用
的方式,但由於HTTP將會傳輸更多的數據,用Base64就太浪費寶貴的帶寬資源了。於是我們的前輩們就想出一個好辦法,在Header里固定增加一個Content-length欄位,用來標示,後續的數據大小。Haeder和content用分割。這樣就解決了問題,有不會造成數據膨脹。(後續增加的trunk模式這裡就不展開說了)
- 當然有朋友就會問了,有沒有可能不增加額外的位元組解決「斷句」問題?答案是:Yes。1999年XMPP協議被制定出來的時候,就實現了這個設想。XMPP是基於XML的協議,XML的語法解析是有能力判斷XML是否閉合,從而完成「斷句」。但這種方式有一個較為嚴重的問題就是,「斷句」邏輯依賴編碼方式,這樣在工程上的實現難度就會變大。
這裡有個有意思的插曲,大家可能發現GFW有封SSH協議的能力,按理說SSH是加密的,不應該能被識別出來啊,看下圖大家就應該明白了:
SSH協議在client連接成功後,server端會主動明文發送ssh的版本信息。5. 複雜度和效率的平衡
首先我們介紹一種叫Netstring Netstring 的協議:開頭用ASCII明文標記後面數據的大小,然後緊跟一個「:」,然後是數據,最後以一個「,」結尾。
12:hello world!,
空消息:
0:,
我們在之前幾家公司做項目的時候為了平衡「工程實現複雜度」和「運行效率」,往往採用類似Netstring的協議,並進一步簡化,固定10bytes的ASCII頭表示消息長度,後面緊跟數據。
0000000002hi
這樣做的優勢有:
- ASCII頭方便debug
- 10bytes固定頭長度,易於演算法實現,消息長度上限大約是10GB,基本夠用
- 頭後面緊跟數據,不需要其它邏輯
應朋友請求打個廣告
歡迎加入Reboot運維&開發技術交流群:365534424
推薦閱讀:
※非 NTFS 的日誌式文件系統上,比如Ext4,能實現類似 Everything 這個軟體的利用日誌進行快速搜索的功能么?
※為什麼 GLIBC 的代碼量這麼大?