什么是RPC?聊聊node中怎么实现 RPC 通信

【相关教程推荐:nodejs视频教程】
什么是RPC?
RPC:Remote Procedure Call(远程过程调用)是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
RPC vs HTTP
相同点
- 都是两台计算机之间的网络通信。ajax是浏览器和服务器之间的通行,RPC是服务器与服务器之间的通行
- 需要双方约定一个数据格式
不同点
- 寻址服务器不同
ajax 是使用 DNS作为寻址服务获取域名所对应的ip地址,浏览器拿到ip地址之后发送请求获取数据。
RPC一般是在内网里面相互请求,所以它一般不用DNS做寻址服务。因为在内网,所以可以使用规定的id或者一个虚拟vip,比如v5:8001,然后到寻址服务器获取v5所对应的ip地址。
- 应用层协议不同
ajax使用http协议,它是一个文本协议,我们交互数据的时候文件格式要么是html,要么是json对象,使用json的时候就是key-value的形式。
RPC采用二进制协议。采用二进制传输,它传输的包是这样子的[0001 0001 0111 0110 0010],里面都是二进制,一般采用那几位表示一个字段,比如前6位是一个字段,依次类推。
这样就不需要http传输json对象里面的key,所以有更小的数据体积。
因为传输的是二进制,更适合于计算机来理解,文本协议更适合人类理解,所以计算机去解读各个字段的耗时是比文本协议少很多的。
- TCP通讯方式
单工通信:只能客户端给服务端发消息,或者只能服务端给客户端发消息
半双工通信:在某个时间段内只能客户端给服务端发消息,过了这个时间段服务端可以给客户端发消息。如果把时间分成很多时间片,在一个时间片内就属于单工通信
全双工通信:客户端和服务端能相互通信
选择这三种通信方式的哪一种主要考虑的因素是:实现难度和成本。全双工通信是要比半双工通信的成本要高的,在某些场景下还是可以考虑使用半双工通信。
ajax是一种半双工通信。http是文本协议,但是它底层是tcp协议,http文本在tcp这一层会经历从二进制数据流到文本的转换过程。
理解RPC只是在更深入地理解前端技术。
buffer编解码二进制数据包
创建buffer
buffer.from: 从已有的数据创建二进制
const buffer1 = Buffer.from('geekbang')
const buffer2 = Buffer.from([0, 1, 2, 3, 4])
<Buffer 67 65 65 6b 62 61 6e 67>
<Buffer 00 01 02 03 04>buffer.alloc: 创建一个空的二进制
const buffer3 = Buffer.alloc(20) <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
往buffer里面写东西
buffer.write(string, offset): 写入字符串buffer.writeInt8(value, offset): int8表示二进制8位(8位表示一个字节)所能表示的整数,offset开始写入之前要跳过的字节数。buffer.writeInt16BE(value, offset): int16(两个字节数),表示16个二进制位所能表示的整数,即32767。超过这个数程序会报错。
const buffer = Buffer.from([1, 2, 3, 4]) // <Buffer 01 02 03 04> // 往第二个字节里面写入12 buffer.writeInt8(12, 1) // <Buffer 01 0c 03 04>
大端BE与小端LE:主要是对于2个以上字节的数据排列方式不同(writeInt8因为只有一个字节,所以没有大端和小端),大端的话就是低位地址放高位,小端就是低位地址放低位。如下:
const buffer = Buffer.from([1, 2, 3, 4]) buffer.writeInt16BE(512, 2) // <Buffer 01 02 02 00> buffer.writeInt16LE(512, 2) // <Buffer 01 02 00 02>
RPC传输的二进制如何表示传递的字段
PC传输的二进制是如何表示字段的呢?现在有个二进制包[00, 00, 00, 00, 00, 00, 00],我们假定前三个字节表示一个字段值,后面两个表示一个字段的值,最后两个也表示一个字段的值。那写法如下:
writeInt16BE(value, 0) writeInt16BE(value, 2) writeInt16BE(value, 4)
发现像这样写,不仅要知道写入的值,还要知道值的数据类型,这样就很麻烦。不如json格式那么方便。针对这种情况业界也有解决方案。npm有个库protocol-buffers,把我们写的参数转化为buffer:
// test.proto 定义的协议文件
message Column {
required float num = 1;
required string payload = 2;
}
// index.js
const fs = require('fs')
var protobuf = require('protocol-buffers')
var messages = protobuf(fs.readFileSync('test.proto'))
var buf = messages.Column.encode({
num: 42,
payload: 'hello world'
})
console.log(buf)
// <Buffer 0d 00 00 28 42 12 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64>
var obj = messages.Column.decode(buf)
console.log(obj)
// { num: 42, payload: 'hello world' }net建立RPC通道
半双工通信
服务端代码:
const net = require('net')
const LESSON_DATA = {
136797: '01 | 课程介绍',
136798: '02 | 内容综述',
136799: '03 | Node.js是什么?',
136800: '04 | Node.js可以用来做什么?',
136801: '05 | 课程实战项目介绍',
136803: '06 | 什么是技术预研?',
136804: '07 | Node.js开发环境安装',
136806: '08 | 第一个Node.js程序:石头剪刀布游戏',
136807: '09 | 模块:CommonJS规范',
136808: '10 | 模块:使用模块规范改造石头剪刀布游戏',
136809: '11 | 模块:npm',
141994: '12 | 模块:Node.js内置模块',
143517: '13 | 异步:非阻塞I/O',
143557: '14 | 异步:异步编程之callback',
143564: '15 | 异步:事件循环',
143644: '16 | 异步:异步编程之Promise',
146470: '17 | 异步:异步编程之async/await',
146569: '18 | HTTP:什么是HTTP服务器?',
146582: '19 | HTTP:简单实现一个HTTP服务器'
}
const server = net.createServer(socket => {
// 监听客户端发送的消息
socket.on('data', buffer => {
const lessonId = buffer.readInt32BE()
setTimeout(() => {
// 往客户端发送消息
socket.write(LESSON_DATA[lessonId])
}, 1000)
})
})
server.listen(4000)客户端代码:
const net = require('net')
const socket = new net.Socket({})
const LESSON_IDS = [
'136797',
'136798',
'136799',
'136800',
'136801',
'136803',
'136804',
'136806',
'136807',
'136808',
'136809',
'141994',
'143517',
'143557',
'143564',
'143644',
'146470',
'146569',
'146582'
]
socket.connect({
host: '127.0.0.1',
port: 4000
})
let buffer = Buffer.alloc(4)
buffer.writeInt32BE(LESSON_IDS[Math.floor(Math.random() * LESSON_IDS.length)])
// 往服务端发送消息
socket.write(buffer)
// 监听从服务端传回的消息
socket.on('data', buffer => {
console.log(buffer.toString())
// 获取到数据之后再次发送消息
buffer = Buffer.alloc(4)
buffer.writeInt32BE(LESSON_IDS[Math.floor(Math.random() * LESSON_IDS.length)])
socket.write(buffer)
})
javascript