RPC调用相比于HTTP效率更高,因为RPC底层基于二进制流进行通讯,信息更为简单。Node.js下的net包是对tcp的封装,因此可以基于net包实现rpc通信。
本DEMO结构如下
RPC
├── client.js
├── clientUtil.js
├── server.js
└── serverUtil.js
首先看一下client端实现
const socket = new net.Socket({})
socket.connect({
host: '127.0.0.1',
port: 4000
})
客户端实现
由net创建一个socket实例后即可进行socket通信
for(let i = 0; i < 100; ++i){
let id = LESSON_IDS[Math.floor(Math.random() * LESSON_IDS.length)]
socket.write(util.encode(id))
}
encode
使用socket.write发送数据,这里随机发送100次假数据,因为是基于二进制流(buffer)进行通信,因此本地的数据需要进行编码/解码才能使用,发送数据前使用encode进行编码,下面看一下encode实现
// clientUtil.js
const encode = (data) =>{
const body = Buffer.alloc(BodyLength)
body.writeUInt32BE(data)
const header = Buffer.alloc(HeaderLength)
header.writeInt16BE(seq++)
header.writeInt32BE(body.length, SeqLength)
const buffer = Buffer.concat([header, body])
console.log(`包${seq}传输的课程id为${data}`);
return buffer
}
在基于二进制流的通信方式中我们设计一个简单的数据结构,该结构包含请求头和请求体,请求头包含序号(Seq)和请求体长度(BodyLength),序号需要占用两字节,请求体长度需要占用四个字节,因此整个header长六个字节。这里body比较小,使用4字节即可保存。
decode
服务端返回的同样是buffer流,因此需要按照约定的结构进行解码,decode函数按照特定结构从buffer中分理出header和body,header内可以获取该包的序号及body长度,body就是server返回的数据。
const decode = (data) =>{
const header = data.slice(0, HeaderLength)
const seq = header.readInt16BE();
const body = data.slice(HeaderLength)
console.log(data)
return{
seq,
data: body.toString()
}
}
解决粘包
平常使用HTTP进行请求时每发送一个request就会收到一个response,但是tcp是基于流的协议,在数据包比较小时会将多个包组合在一起进行发送,因此需要按照特定的结构进行拆包(BodyLength用于标识body长度,有了body长度才能拆包)
下面看一下一个简单的拆包代码
//因为已知header长度,从header中获取bodyLength即可拆出每个包
socket.on('data', (buffer) =>{
while(buffer.length >= 6){
const header = buffer.slice(0, util.HeaderLength)
const bodyLength = header.readInt32BE(util.SeqLength)
const package = buffer.slice(0, util.HeaderLength + bodyLength)
buffer = buffer.slice(util.HeaderLength + bodyLength)
const result = util.decode(package)
console.log(`包${result.seq},返回值是${result.data}`);
}
})
服务端实现
服务端与客户端基本相同,简单贴一下代码
//server.js
const server = net.createServer((socket) =>{
socket.on('data', (buffer) =>{
while(buffer.length >= 6){
const package = buffer.slice(0, util.PackageLength)
buffer = buffer.slice(util.PackageLength)
const result = util.decode(package)
socket.write(
util.encode(LESSON_DATA[result.data], result.seq)
);
}
})
})
//serverUtil.js
const encode = (data, seq) =>{
const body = Buffer.from(data)
const header = Buffer.alloc(HeaderLength)
header.writeInt16BE(seq)
header.writeInt32BE(body.length, SeqLength)
const buffer = Buffer.concat([header, body])
console.log(buffer)
return buffer
}
const decode = (buffer) =>{
const header = buffer.slice(0, HeaderLength)
const seq = header.readInt16BE()
const body = buffer.slice(6).readInt32BE()
return{
seq,
data:body
}
}