理解基于 TCP 的应用层通信协议

TCP 协议示意


TCP协议

关于七层网络通信的基本原理,特别推荐这篇图文并茂的长文《TCP/IP笔记 - 综述》

TCP 通信基本特征


TCP数据流

特征

1. 消息(结构化数据)被编码成字节流写入 TCP 通道。

2. TCP 通道不能保证字节流一定到达目的地,但能保证到达的字节流是 正确有序 的。对于发送端而言,可以不停的写入数据,当网络出问题 ACK 超时会报错,但由于缓存的存在,发送端其实不知道有多少数据到达接收端。对于接收端而言,一直等待接收数据,一旦收到数据是能确定这些数据是连续、有序的,中间不可能有数据缺失,但接收端无法知道何时能收到下一个数据包。

3. TCP 通道像一个无形的管道,这个管道的流量由全链路复杂的网络环境决定,TCP 协议会自动调节(即拥塞控制)。

4. TCP 是全双工协议,读写互不干扰。注意,如图所示,读写是两个完全不同的通道,它们完全可能走不同的物理链路。

TCP控制流程

疑问

1. 对于接收端来说,虽然能接收到正确的有序的字节流,如何界定收到的字节流构成一个完整的消息体?这就是所谓 “”粘包” 问题。

2. 对于接收端来说,一直在等待读取数据,如何判断发送端是空闲还是失联?这就是 超时问题。

基于 TCP 构建数据通信协议,首先要解决的就是上面两个问题。另外,不管是客户端还是服务端,它们都同时是发送端和接收端。从应用层面来说,我们还需要构建 请求(Request)/响应(Response) 机制,比如浏览器调用后端 API 服务需要知道结果,或者消息服务器往客户端推送消息需要知道消息是否被客户端处理。当然也有一种消息从发送端发出后是不需要知道结果的,这种消息通常称为 通知(Notification)

基于 TCP 协议构建应用层的通信协议

两个模式和三个问题

TCP 协议本质是流模式,基于它可以构建各种应用层通信协议,但其基本模式只有两种:

1. Streaming 流模式,如 HTTP/1 协议,redis 协议。

2. Multiplexing 多路复用模式,如 MongoDB 协议、常见的 RPC 协议。

随着技术的发展,也出现了这两种基本模式的混合体:

1. Streaming + Multiplexing,如基于 HTTP/1 实现的 JSON-RPC 协议。

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

根据 TCP 协议的特征,我们应用层协议一般要解决三个问题:

1. 粘包问题,从字节流中分解出一个个独立的消息体;

2. 请求/响应机制;

3. 消息指令或类型定义,解决超时问题和实际应用的通信需求。

Streaming

最原始的 Streaming 模式就是一应一答模式。

相信不少人基于 TCP 开发网络通信时干过这种事:把一个请求数据变成字节写入 TCP,再等待对方的应答数据,收到应答后开始下一个请求。HTTP/1.0 就是这个模式的最典型。

用上面的管道示意图来理解,每次往管道放入一个数据包,然后等对方回复一个数据包,从而实现应用层需要的 请求(Request)/响应(Response) 机制。

这种模式下通信效率显然特别低,为了提升效率得开多个 TCP 通道,然而打开 TCP 通道不但有三次握手开销,还给服务器带来一定资源开销压力,特别是 Apache 那种传统的 web 服务。

既然是管道,其实是可以像流体一样不断的写入数据包,只要定义 请求(Request)/响应(Response) 的逻辑关系,这就是 Pipelining 机制,HTTP/1.1 和 redis 协议属于这种模式。

流水线请求应答模式

比如 HTTP/1.1 协议,允许客户端依次写入多个请求而无需等待应答,服务端则应该按照客户端的请求顺序依次进行响应,从而确保 请求(Request)/响应(Response) 一一对应。

HTTP/1

HTTP/1 是基于文本的协议:

1. 通过 CRLF (也就是 \r\n) 标志解决粘包问题,如果内容中有 \r\n,必须进行转义,第一行命令行、Headers 头部和实体主体各有不同的转义处理。

2. 请求/响应机制就是一应一答模式或者 Pipelining。

3. 第一行定义了丰富的消息类型,超时机制则是在浏览器或者服务端逻辑实现,协议层没有定义。

HTTP 请求协议


HTTP 响应协议

Redis 的 RESP 协议

RESP 也是基于文本的协议:

1. 定义了五种消息类型,分别由 +-:$* 字符开头,CRLF 结尾,其中 Bulk Strings 类型允许包含 CRLF。

2. 请求/响应机制是复杂的 Pipelining,允许客户端不断的写入请求,服务端会按照顺序响应请求。但是,根据请求命令的不同,对应响应体会有 零到无数 个。

3. 协议层定义了五种消息类型,其中的 Arrays 是结构化消息类型。对比 HTTP 协议,RESP 协议不用依赖于更上一层的 JSON、XML 协议等,就能构造出复杂的消息体。超时问题依然由客户端或服务端的逻辑实现。

RESP 协议

HTTP/1 协议和 RESP 协议可以算是我们当前使用最广泛的协议,有很多服务都是基于 RESP 协议。然而,即便应用最广,也有 Pipelining 机制,基于 Streaming 的协议依然有一个痛点:头部阻塞,也就是如果某一个请求需要消耗很长处理时间才能响应,后续响应都得排队等候,即被阻塞。

Multiplexing

这是一种解决头部阻塞问题的更高效的模式,它不在依赖于 请求(Request)/响应(Response) 的顺序处理,允许请求并发发出,请求处理完成就立即响应,其核心就是 Request ID


Multiplexing

MongoDB 协议

MongoDB 协议 是基于二进制的协议,协议定义的内容很丰富:

1. 一个完整消息由 Header 和 Body 组成。Header 有 16 位,定义如下,Body 的长度则在 Header 中的 messageLength 定义,编码格式则是 bson。

2. Header 中的 requestID 是请求 ID,responseTo 是响应 ID,所以其请求/响应机制是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 之前,写入、更新、删除操作等是没有响应结果的!那么如何确定写入是否成功呢?通过 getLastError 命令,这个命令是基于 OP_QUERY 的。每一个写入操作追加一个 getLastError 请求,查询上一次命令是否报错(很笨的设计有没有?相当于回退到一应一答的 Streaming 模式了)。

MongoDB 2.6 之后使用了 maxWireVersion 3 协议,扩展了数据库的 commands,可以进行各种各样的操作,可以在客户端使用 db.$command.help() 查看所有命令。而 commands 的本质就是 OP_QUERY 类型的查询,所以第三代协议相当于只使用 OP_QUERYOP_GET_MOREOP_REPLY 类型的消息,淘汰了其它类型。

TiKV 协议

TiKV 协议 是基于二进制的协议:

1. 一个完整消息由 Header 和 Body 组成。Header 有 16 位,定义如下,Body 的长度则在 Header 中的 payload_len 定义,编码格式由 protobuf 定义。

2. Header 中的 msg_id 是请求 ID,所以其请求/响应机制是 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 出现引发的,很多人基于 HTTP 来实现 JSON-RPC 服务。

JSON-RPC 2.0

JSON-RPC 2.0 定义两类四种类型的消息,分别是:

1. Request object:

定义了 jsonrpcmethodparamsid 四种属性,当 id 存在时,则为标准的 Request,如

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

Request 需要对方进行响应;不存在时则为 Notification,如

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

Notification 不需要对方响应。

2. Response object:

包括 jsonrpcresulterrorid 四种属性,id 必须存在,result, error 只能有一个存在,当 result 存在时,则为 Success Response,如

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

error 存在时,则为 Error Response,如

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

另外也有 RESP 协议配合 JSON-RPC 2.0 实现的 RPC 框架 toa-net,主要是利用 RESP 协议解决粘包问题,JSON-RPC 2.0 协议解决 Multiplexing 模式的 请求(Request)/响应(Response)

Multiplexing + Streaming

HTTP/2

HTTP/2 协议在一个 TCP 通道建立了 N 个 Stream 流通道,每个 Stream 有唯一的 ID,从而实现 Multiplexing 模式,Stream 内则与原来的 HTTP/1 一样,是 Streaming 模式。

HTTP/2
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容