Remote Procedure Call 远程过程调用
rpc vs ajax
相同点: 两台计算机之间的网络通信 双方约定数据格式
不同点: RPC不一定使用DNS作为寻址服务(一般在内网内相互请求) 应用层协议一般不适用HTTP(二进制协议,更小的数据包体积,更快的编解码速率) ajax -> http协议(文本协议) 基于TCP或UDP协议
寻址过程: ajax通过DNS解析拿到IP,再请求 rpc通过ID拿到IP,在请求
TCP通信方式:
- 单工通信 demo
- 半双工通信(同一时间内,只有一端能向另一端发送)
- 全双工通信(同一时间内,两端能相互收发消息)
nodejs Buffer 编解码二进制数据包
protocol buffer 二进制协议编码库
因为在 TCP 通道里传输的数据只能是二进制形式的,所以我们必须将数据结构或对象转换成二进制串传递给对方,这个过程就叫「序列化」。而相反,我们收到对方的二进制串后把它转换成数据结构或对象的过程叫「反序列化」。而序列化和反序列化的规则就叫「协议」。
把 RPC 的协议分成两大类,一类是通讯层协议,另一类是应用层协议。通讯层协议一般是和业务无关的,它的职责是将业务数据打包后,安全、完整的传输给接受方,HSF、Dubbo、gRPC 这些都是属于通讯层协议。而应用层协议是约定业务数据和二进制串的转换规则,常见的应用层协议有 Hessian,Protobuf,JSON。这两种协议的关注点是不太一样的,对于一个 RPC 框架来说,通讯层协议一旦确定就很少变化,这要求它具备足够好的通用性和扩展性;而应用层协议理论上可以由业务自由选择,它更多的是关注编码的效率和跨语言等特性。RPC 框架的核心是通讯层协议的设计,换句话说你理解了通讯层协议各个字段的含义,基本上也理解了 RPC 原理。
协议设计就像把一个数据包按顺序切分成若干个单位长度的「小格子」,然后约定每个「小格子」里存储什么样的信息,一个「小格子」就是一个 Byte,它是协议设计的最小单位,1 Byte 是 8 Bit,可以描述 0 ~ 2^8 个字节数,具体使用多少个字节要看实际存储的信息。我们在收到一个数据包的时候首先确定它是请求还是响应,所以我们需要用一个 Byte 来标记包的类型,比如:0 表示请求,1 表示响应。知道包类型后,我们还需要将请求和它对应的响应关联起来,通常的做法是在请求前生成一个「唯一」的 ID,放到 Header 里传递给服务端,服务端在返回的响应头里也要包含同样的 ID,这个 ID 我们选择用一个 Int32 类型(4 Bytes)自增的数字表示。要能实现包的准确切割,我们需要明确包的长度,Header 长度通常是固定的,而 Payload 长度是变化的,所以要在 Header 留 4 个 Bytes(Int32) 记录 Payload 部分的长度。确定包长度后,我们就可以切分出一个个独立的包。Payload 部分编码规则由应用层协决定,不同的场景采用的协议可能是不一样的,那么接收端如何知道用什么协议去解码 Payload 部分呢?所以,在 Header 里面还需要一个 Byte 标记应用层协议的类型,我们称之为 Codec。现在来看看我们设计的协议长什么样:
0 1 2 3 4 5 6 7 8 9 10
+------+------+------+------+------+------+------+------+------+------+
| type | requestId | codec| bodyLength |
+------+---------------------------+------+---------------------------+
| ... payload |
| ... |
+---------------------------------------------------------------------+
多路复用RPC通道
Node.js 的 Net模块用于创建基于流的 TCP 或 IPC 的服务器(net.createServer())与客户端(net.createConnection())。
Node.js 的 Net模块是基于TCP协议的socket网路编程模块。
半双工通信需要带上序号,才能转换为全双工通信。如果服务端响应不带上序号,一旦发生延迟等情况,客户端无法确认响应是哪个请求的,请求包和返回包错乱。需要添加包序号确认顺序。
tcp底层会自动把同时发的包拼接起来,拼成打包 -> 粘包,需要分包操作 demo
则需要自动拆解包 demo