理解基于 TCP 的應(yīng)用層通信協(xié)議

TCP 協(xié)議示意


TCP協(xié)議

關(guān)于七層網(wǎng)絡(luò)通信的基本原理,特別推薦這篇圖文并茂的長文《TCP/IP筆記 - 綜述》

TCP 通信基本特征


TCP數(shù)據(jù)流

特征

1. 消息(結(jié)構(gòu)化數(shù)據(jù))被編碼成字節(jié)流寫入 TCP 通道。

2. TCP 通道不能保證字節(jié)流一定到達(dá)目的地,但能保證到達(dá)的字節(jié)流是?正確、有序?的。對于發(fā)送端而言,可以不停的寫入數(shù)據(jù),當(dāng)網(wǎng)絡(luò)出問題 ACK 超時(shí)會(huì)報(bào)錯(cuò),但由于緩存的存在,發(fā)送端其實(shí)不知道有多少數(shù)據(jù)到達(dá)接收端。對于接收端而言,一直等待接收數(shù)據(jù),一旦收到數(shù)據(jù)是能確定這些數(shù)據(jù)是連續(xù)、有序的,中間不可能有數(shù)據(jù)缺失,但接收端無法知道何時(shí)能收到下一個(gè)數(shù)據(jù)包。

3. TCP 通道像一個(gè)無形的管道,這個(gè)管道的流量由全鏈路復(fù)雜的網(wǎng)絡(luò)環(huán)境決定,TCP 協(xié)議會(huì)自動(dòng)調(diào)節(jié)(即擁塞控制)。

4. TCP 是全雙工協(xié)議,讀寫互不干擾。注意,如圖所示,讀寫是兩個(gè)完全不同的通道,它們完全可能走不同的物理鏈路。

TCP控制流程

疑問

1. 對于接收端來說,雖然能接收到正確的有序的字節(jié)流,如何界定收到的字節(jié)流構(gòu)成一個(gè)完整的消息體?這就是所謂 “”粘包” 問題。

2. 對于接收端來說,一直在等待讀取數(shù)據(jù),如何判斷發(fā)送端是空閑還是失聯(lián)?這就是?超時(shí)問題。

基于 TCP 構(gòu)建數(shù)據(jù)通信協(xié)議,首先要解決的就是上面兩個(gè)問題。另外,不管是客戶端還是服務(wù)端,它們都同時(shí)是發(fā)送端和接收端。從應(yīng)用層面來說,我們還需要構(gòu)建?請求(Request)/響應(yīng)(Response)?機(jī)制,比如瀏覽器調(diào)用后端 API 服務(wù)需要知道結(jié)果,或者消息服務(wù)器往客戶端推送消息需要知道消息是否被客戶端處理。當(dāng)然也有一種消息從發(fā)送端發(fā)出后是不需要知道結(jié)果的,這種消息通常稱為 通知(Notification)。

基于 TCP 協(xié)議構(gòu)建應(yīng)用層的通信協(xié)議

兩個(gè)模式和三個(gè)問題

TCP 協(xié)議本質(zhì)是流模式,基于它可以構(gòu)建各種應(yīng)用層通信協(xié)議,但其基本模式只有兩種:

1. Streaming 流模式,如 HTTP/1 協(xié)議,redis 協(xié)議。

2. Multiplexing 多路復(fù)用模式,如 MongoDB 協(xié)議、常見的 RPC 協(xié)議。

隨著技術(shù)的發(fā)展,也出現(xiàn)了這兩種基本模式的混合體:

1. Streaming + Multiplexing,如基于 HTTP/1 實(shí)現(xiàn)的 JSON-RPC 協(xié)議。

2. Multiplexing + Streaming,如 HTTP/2,基于 HTTP/2 的 gRPC 等。

根據(jù) TCP 協(xié)議的特征,我們應(yīng)用層協(xié)議一般要解決三個(gè)問題:

1. 粘包問題,從字節(jié)流中分解出一個(gè)個(gè)獨(dú)立的消息體;

2. 請求/響應(yīng)機(jī)制;

3. 消息指令或類型定義,解決超時(shí)問題和實(shí)際應(yīng)用的通信需求。

Streaming

最原始的 Streaming 模式就是一應(yīng)一答模式。

相信不少人基于 TCP 開發(fā)網(wǎng)絡(luò)通信時(shí)干過這種事:把一個(gè)請求數(shù)據(jù)變成字節(jié)寫入 TCP,再等待對方的應(yīng)答數(shù)據(jù),收到應(yīng)答后開始下一個(gè)請求。HTTP/1.0 就是這個(gè)模式的最典型。

用上面的管道示意圖來理解,每次往管道放入一個(gè)數(shù)據(jù)包,然后等對方回復(fù)一個(gè)數(shù)據(jù)包,從而實(shí)現(xiàn)應(yīng)用層需要的 請求(Request)/響應(yīng)(Response) 機(jī)制。

這種模式下通信效率顯然特別低,為了提升效率得開多個(gè) TCP 通道,然而打開 TCP 通道不但有三次握手開銷,還給服務(wù)器帶來一定資源開銷壓力,特別是 Apache 那種傳統(tǒng)的 web 服務(wù)。

既然是管道,其實(shí)是可以像流體一樣不斷的寫入數(shù)據(jù)包,只要定義 請求(Request)/響應(yīng)(Response) 的邏輯關(guān)系,這就是 Pipelining 機(jī)制,HTTP/1.1 和 redis 協(xié)議屬于這種模式。

流水線請求應(yīng)答模式

比如 HTTP/1.1 協(xié)議,允許客戶端依次寫入多個(gè)請求而無需等待應(yīng)答,服務(wù)端則應(yīng)該按照客戶端的請求順序依次進(jìn)行響應(yīng),從而確保 請求(Request)/響應(yīng)(Response) 一一對應(yīng)。

HTTP/1

HTTP/1 是基于文本的協(xié)議:

1. 通過 CRLF (也就是 \r\n) 標(biāo)志解決粘包問題,如果內(nèi)容中有 \r\n,必須進(jìn)行轉(zhuǎn)義,第一行命令行、Headers 頭部和實(shí)體主體各有不同的轉(zhuǎn)義處理。

2. 請求/響應(yīng)機(jī)制就是一應(yīng)一答模式或者 Pipelining。

3. 第一行定義了豐富的消息類型,超時(shí)機(jī)制則是在瀏覽器或者服務(wù)端邏輯實(shí)現(xiàn),協(xié)議層沒有定義。

HTTP 請求協(xié)議


HTTP 響應(yīng)協(xié)議

Redis 的 RESP 協(xié)議

RESP 也是基于文本的協(xié)議:

1. 定義了五種消息類型,分別由 +、-:、$、* 字符開頭,CRLF 結(jié)尾,其中 Bulk Strings 類型允許包含 CRLF。

2. 請求/響應(yīng)機(jī)制是復(fù)雜的 Pipelining,允許客戶端不斷的寫入請求,服務(wù)端會(huì)按照順序響應(yīng)請求。但是,根據(jù)請求命令的不同,對應(yīng)響應(yīng)體會(huì)有 零到無數(shù) 個(gè)。

3. 協(xié)議層定義了五種消息類型,其中的 Arrays 是結(jié)構(gòu)化消息類型。對比 HTTP 協(xié)議,RESP 協(xié)議不用依賴于更上一層的 JSON、XML 協(xié)議等,就能構(gòu)造出復(fù)雜的消息體。超時(shí)問題依然由客戶端或服務(wù)端的邏輯實(shí)現(xiàn)。

RESP 協(xié)議

HTTP/1 協(xié)議和 RESP 協(xié)議可以算是我們當(dāng)前使用最廣泛的協(xié)議,有很多服務(wù)都是基于 RESP 協(xié)議。然而,即便應(yīng)用最廣,也有 Pipelining 機(jī)制,基于 Streaming 的協(xié)議依然有一個(gè)痛點(diǎn):頭部阻塞,也就是如果某一個(gè)請求需要消耗很長處理時(shí)間才能響應(yīng),后續(xù)響應(yīng)都得排隊(duì)等候,即被阻塞。

Multiplexing

這是一種解決頭部阻塞問題的更高效的模式,它不在依賴于 請求(Request)/響應(yīng)(Response) 的順序處理,允許請求并發(fā)發(fā)出,請求處理完成就立即響應(yīng),其核心就是 Request ID


Multiplexing

MongoDB 協(xié)議

MongoDB 協(xié)議?是基于二進(jìn)制的協(xié)議,協(xié)議定義的內(nèi)容很豐富:

1. 一個(gè)完整消息由 Header 和 Body 組成。Header 有 16 位,定義如下,Body 的長度則在 Header 中的 messageLength 定義,編碼格式則是 bson。

2. Header 中的 requestID 是請求 ID,responseTo 是響應(yīng) ID,所以其請求/響應(yīng)機(jī)制是Multiplexing。

3. Header 中的 opCode 定義了 11 中消息類型:

struct MsgHeader {

? int32 ?messageLength; // total message size, including this

? int32 ?requestID; // identifier for this message

? int32 ?responseTo; // requestID from the original request

? int32 ?opCode; // request type - see table below

}

Opcode 表:

| Opcode Name | Value | Comment |

|----------|-------|--------------|

| OP_REPLY | 1 | Reply to a client request. responseTo is set. |

| OP_MSG | 1000 | Generic msg command followed by a string. |

| OP_UPDATE | 2001 | Update document. |

| OP_INSERT | 2002 | Insert new document. |

| RESERVED | 2003 | Formerly used for OP_GET_BY_OID. |

| OP_QUERY | 2004 | Query a collection. |

| OP_GET_MORE | 2005 | Get more data from a query. See Cursors. |

| OP_DELETE | 2006 | Delete documents. |

| OP_KILL_CURSORS | 2007 | Notify database that the client has finished with the cursor. |

| OP_COMMAND | 2010 | Cluster internal protocol representing a command request. |

| OP_COMMANDREPLY | 2011 | Cluster internal protocol representing a reply to an OP_COMMAND. |

注意,只有 OP_QUERYOP_GET_MORE 兩種類型有 requestID,其它類型都沒有!

所以,在 MongoDB 2.6 之前,寫入、更新、刪除操作等是沒有響應(yīng)結(jié)果的!那么如何確定寫入是否成功呢?通過 getLastError 命令,這個(gè)命令是基于 OP_QUERY 的。每一個(gè)寫入操作追加一個(gè) getLastError?請求,查詢上一次命令是否報(bào)錯(cuò)(很笨的設(shè)計(jì)有沒有?相當(dāng)于回退到一應(yīng)一答的 Streaming 模式了)。

MongoDB 2.6 之后使用了 maxWireVersion 3 協(xié)議,擴(kuò)展了數(shù)據(jù)庫的 commands,可以進(jìn)行各種各樣的操作,可以在客戶端使用 db.$command.help() 查看所有命令。而 commands 的本質(zhì)就是 OP_QUERY 類型的查詢,所以第三代協(xié)議相當(dāng)于只使用 OP_QUERY 、OP_GET_MOREOP_REPLY 類型的消息,淘汰了其它類型。

TiKV 協(xié)議

TiKV 協(xié)議?是基于二進(jìn)制的協(xié)議:

1. 一個(gè)完整消息由 Header 和 Body 組成。Header 有 16 位,定義如下,Body 的長度則在 Header 中的 payload_len 定義,編碼格式由 protobuf 定義。

2. Header 中的 msg_id 是請求 ID,所以其請求/響應(yīng)機(jī)制是 Multiplexing

3. 沒有定義消息類型,消息類型由 protobuf 精確定義

struct MsgHeader {

? uint16 ?MSG_MAGIC; // const MSG_MAGIC: u16 = 0xdaf4;

? uint16 ?MSG_VERSION_V1; // const MSG_VERSION_V1: u16 = 1;

? uint32 ?payload_len; // Body length

? uint64 ?msg_id; // request ID

}

Streaming + Multiplexing

這種模式是由 JSON-RPC 2.0 specifications 出現(xiàn)引發(fā)的,很多人基于 HTTP 來實(shí)現(xiàn) JSON-RPC 服務(wù)。

JSON-RPC 2.0

JSON-RPC 2.0 定義兩類四種類型的消息,分別是:

1. Request object:

定義了 jsonrpc,?method,?params,?id?四種屬性,當(dāng) id 存在時(shí),則為標(biāo)準(zhǔn)的 Request,如

{"jsonrpc":"2.0","method":"subtract","params": [42,23],"id":1}

Request 需要對方進(jìn)行響應(yīng);不存在時(shí)則為 Notification,如

{"jsonrpc":"2.0","method":"update","params": [1,2,3,4,5]}

Notification 不需要對方響應(yīng)。

2. Response object:

包括?jsonrpc,?result,?error,?id?四種屬性,id 必須存在,result, error 只能有一個(gè)存在,當(dāng) result 存在時(shí),則為 Success Response,如

{"jsonrpc":"2.0","result":19,"id":1}

當(dāng) error 存在時(shí),則為 Error Response,如

{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":"5"}

另外也有 RESP 協(xié)議配合 JSON-RPC 2.0 實(shí)現(xiàn)的 RPC 框架 toa-net,主要是利用 RESP 協(xié)議解決粘包問題,JSON-RPC 2.0?協(xié)議解決 Multiplexing 模式的 請求(Request)/響應(yīng)(Response)

Multiplexing + Streaming

HTTP/2

HTTP/2 協(xié)議在一個(gè) TCP 通道建立了 N 個(gè) Stream 流通道,每個(gè) Stream 有唯一的 ID,從而實(shí)現(xiàn) Multiplexing 模式,Stream 內(nèi)則與原來的 HTTP/1 一樣,是 Streaming 模式。

HTTP/2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容