基于net包实现多路复用的RPC通道 DEMO


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字节即可保存。

header

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
    }
}

Author: Maple
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Maple !
  TOC