Table of Contents
What is RPC?
RPC vs HTTP
buffer encoding and decoding binary data packet
Create buffer
Write something into the buffer
How does the binary transmitted by RPC represent the fields passed
net建立RPC通道
半双工通信
全双工通信
错位处理
粘包处理
Home Web Front-end JS Tutorial What is RPC? Let's talk about how to implement RPC communication in node

What is RPC? Let's talk about how to implement RPC communication in node

Nov 03, 2022 pm 08:21 PM
node.js rpc

What is RPC? Let's talk about how to implement RPC communication in node

[Related tutorial recommendations: nodejs video tutorial]

What is RPC?

RPC: Remote Procedure Call refers to remote procedure call, that is to say, there are two servers A and B. An application is deployed on server A and you want to call the application on server B. The functions/methods provided cannot be called directly because they are not in the same memory space. The semantics of the call and the data of the call need to be conveyed through the network.

Communication between servers

RPC vs HTTP

Same points

  • are all network communications between two computers. Ajax is the communication between the browser and the server, and RPC is the communication between the server and the server.
  • Requires both parties to agree on a data format

Differences

  • The addressing server is different

ajax uses DNS as the addressing service to obtain the IP address corresponding to the domain name, browse After getting the IP address, the server sends a request to obtain the data.

RPC usually requests each other in the intranet, so it generally does not use DNS for addressing services. Because it is on the internal network, you can use a specified ID or a virtual VIP, such as v5:8001, and then go to the addressing server to obtain the IP address corresponding to v5.

  • The application layer protocols are different

ajaxUse the http protocol, which is a text protocol. When we interact with data, the file format is either html, Either it is a json object, which is in the form of key-value when using json.

RPC adopts binary protocol. Using binary transmission, the packet it transmits looks like this [0001 0001 0111 0110 0010], which is all binary. Generally, those digits are used to represent a field. For example, the first 6 digits are a field, and so on.

This way there is no need for http to transmit the key in the json object, so the data volume is smaller.

Because the transmission is binary, it is more suitable for computers to understand, and the text protocol is more suitable for human understanding, so the computer takes much less time to interpret each field than the text protocol.

RPC uses binary to have smaller data volume and faster interpretation speed.

  • TCP communication method
  • Simplex communication: Only the client can send messages to the server, or only the server can send messages to the server. Client sends messages

  • Half-duplex communication: Within a certain period of time, only the client can send messages to the server. After this period, the server can send messages to the client. . If time is divided into many time slices, within one time slice it is simplex communication

  • Full-duplex communication: the client and the server can communicate with each other

The main factors to consider when choosing one of these three communication methods are: implementation difficulty and cost. Full-duplex communication is more expensive than half-duplex communication. In some scenarios, half-duplex communication can still be considered.

ajax is a half-duplex communication. http is a text protocol, but its bottom layer is a tcp protocol. The http text will undergo a conversion process from binary data flow to text at the tcp layer.

UnderstandingRPC is just a deeper understanding of front-end technology.

buffer encoding and decoding binary data packet

Create buffer

buffer.from: from Some data creates a binary

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>
Copy after login

buffer.alloc: Create an empty binary

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>
Copy after login

Write something into the buffer

  • buffer.write(string, offset): Write a string
  • buffer.writeInt8(value, offset): int8 represents 8-bit binary ( 8 bits represent an integer that can be represented by one byte), and offset is the number of bytes to be skipped before starting to write.
  • buffer.writeInt16BE(value, offset): int16 (two bytes), which represents an integer that can be represented by 16 binary bits, that is, 32767. The program will report an error if it exceeds this number.
const buffer = Buffer.from([1, 2, 3, 4]) // <Buffer 01 02 03 04>

// 往第二个字节里面写入12
buffer.writeInt8(12, 1) // <Buffer 01 0c 03 04>
Copy after login

Big-endian BE and little-endian LE: The main reason is that the data arrangement of more than 2 bytes is different (writeInt8 has only one byte, so there is no big-endian and Little endian), in big endian, the low-order address is placed in the high-order bit, and in little endian, the low-order address is placed in the low-order bit. As follows:

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>
Copy after login

How does the binary transmitted by RPC represent the fields passed

How does the binary transmitted by PC represent the fields? Now there is a binary package [00, 00, 00, 00, 00, 00, 00]. We assume that the first three bytes represent a field value, the next two bytes represent the value of a field, and the last two bytes also represent the value of a field. value. The writing method is as follows:

writeInt16BE(value, 0)
writeInt16BE(value, 2)
writeInt16BE(value, 4)
Copy after login

发现像这样写,不仅要知道写入的值,还要知道值的数据类型,这样就很麻烦。不如json格式那么方便。针对这种情况业界也有解决方案。npm有个库protocol-buffers,把我们写的参数转化为buffer

// test.proto 定义的协议文件
message Column {
  required float num  = 1;
  required string payload = 2;
}
// index.js
const fs = require(&#39;fs&#39;)
var protobuf = require(&#39;protocol-buffers&#39;)
var messages = protobuf(fs.readFileSync(&#39;test.proto&#39;))

var buf = messages.Column.encode({
	num: 42,
	payload: &#39;hello world&#39;
})
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: &#39;hello world&#39; }
Copy after login

net建立RPC通道

半双工通信

服务端代码:

const net = require(&#39;net&#39;)

const LESSON_DATA = {
  136797: &#39;01 | 课程介绍&#39;,
  136798: &#39;02 | 内容综述&#39;,
  136799: &#39;03 | Node.js是什么?&#39;,
  136800: &#39;04 | Node.js可以用来做什么?&#39;,
  136801: &#39;05 | 课程实战项目介绍&#39;,
  136803: &#39;06 | 什么是技术预研?&#39;,
  136804: &#39;07 | Node.js开发环境安装&#39;,
  136806: &#39;08 | 第一个Node.js程序:石头剪刀布游戏&#39;,
  136807: &#39;09 | 模块:CommonJS规范&#39;,
  136808: &#39;10 | 模块:使用模块规范改造石头剪刀布游戏&#39;,
  136809: &#39;11 | 模块:npm&#39;,
  141994: &#39;12 | 模块:Node.js内置模块&#39;,
  143517: &#39;13 | 异步:非阻塞I/O&#39;,
  143557: &#39;14 | 异步:异步编程之callback&#39;,
  143564: &#39;15 | 异步:事件循环&#39;,
  143644: &#39;16 | 异步:异步编程之Promise&#39;,
  146470: &#39;17 | 异步:异步编程之async/await&#39;,
  146569: &#39;18 | HTTP:什么是HTTP服务器?&#39;,
  146582: &#39;19 | HTTP:简单实现一个HTTP服务器&#39;
}

const server = net.createServer(socket => {
  // 监听客户端发送的消息
  socket.on(&#39;data&#39;, buffer => {
    const lessonId = buffer.readInt32BE()
    setTimeout(() => {
      // 往客户端发送消息
      socket.write(LESSON_DATA[lessonId])
    }, 1000)
  })
})

server.listen(4000)
Copy after login

客户端代码:

const net = require(&#39;net&#39;)

const socket = new net.Socket({})

const LESSON_IDS = [
  &#39;136797&#39;,
  &#39;136798&#39;,
  &#39;136799&#39;,
  &#39;136800&#39;,
  &#39;136801&#39;,
  &#39;136803&#39;,
  &#39;136804&#39;,
  &#39;136806&#39;,
  &#39;136807&#39;,
  &#39;136808&#39;,
  &#39;136809&#39;,
  &#39;141994&#39;,
  &#39;143517&#39;,
  &#39;143557&#39;,
  &#39;143564&#39;,
  &#39;143644&#39;,
  &#39;146470&#39;,
  &#39;146569&#39;,
  &#39;146582&#39;
]

socket.connect({
  host: &#39;127.0.0.1&#39;,
  port: 4000
})

let buffer = Buffer.alloc(4)
buffer.writeInt32BE(LESSON_IDS[Math.floor(Math.random() * LESSON_IDS.length)])

// 往服务端发送消息
socket.write(buffer)

// 监听从服务端传回的消息
socket.on(&#39;data&#39;, buffer => {
  console.log(buffer.toString())

  // 获取到数据之后再次发送消息
  buffer = Buffer.alloc(4)
  buffer.writeInt32BE(LESSON_IDS[Math.floor(Math.random() * LESSON_IDS.length)])

  socket.write(buffer)
})
Copy after login

以上半双工通信步骤如下:

  • 客户端发送消息 socket.write(buffer)
  • 服务端接受消息后往客户端发送消息 socket.write(buffer)
  • 客户端接受消息后再次发送消息

这样在一个时间端之内,只有一个端往另一个端发送消息,这样就实现了半双工通信。那如何实现全双工通信呢,也就是在客户端往服务端发送消息的同时,服务端还没有消息返回给客户端之前,客户端又发送了一个消息给服务端。

全双工通信

先来看一个场景:

What is RPC? Lets talk about how to implement RPC communication in node

客户端发送了一个id1的请求,但是服务端还来不及返回,接着客户端又发送了一个id2的请求。

等了一个之后,服务端先把id2的结果返回了,然后再把id1的结果返回。

那如何结果匹配到对应的请求上呢?

如果按照时间顺序,那么id1的请求对应了id2的结果,因为id2是先返回的;id2的请求对应了id1的结果,这样就导致请求包和返回包错位的情况。

怎么办呢?

我们可以给请求包和返回包都带上序号,这样就能对应上。

错位处理

客户端代码:

socket.on(&#39;data&#39;, buffer => {
  // 包序号
  const seqBuffer = buffer.slice(0, 2)
  // 服务端返回的内容
  const titleBuffer = buffer.slice(2)
    
  console.log(seqBuffer.readInt16BE(), titleBuffer.toString())
})

// 包序号
let seq = 0
function encode(index) {
  // 请求包的长度现在是6 = 2(包序号) + 4(课程id)
  buffer = Buffer.alloc(6)
  buffer.writeInt16BE(seq)
  buffer.writeInt32BE(LESSON_IDS[index], 2)

  seq++
  return buffer
}

// 每50ms发送一次请求
setInterval(() => {
  id = Math.floor(Math.random() * LESSON_IDS.length)
  socket.write(encode(id))
}, 50)
Copy after login

服务端代码:

const server = net.createServer(socket => {
  socket.on(&#39;data&#39;, buffer => {
    // 把包序号取出
    const seqBuffer = buffer.slice(0, 2)
    // 从第2个字节开始读取
    const lessonId = buffer.readInt32BE(2)
    setTimeout(() => {
      const buffer = Buffer.concat([
        seqBuffer,
        Buffer.from(LESSON_DATA[lessonId])
      ])
      socket.write(buffer)
      // 这里返回时间采用随机的,这样就不会按顺序返回,就可以测试错位的情况
    }, 10 + Math.random() * 1000)
  })
})
Copy after login
  • 客户端把包序号和对应的id给服务端
  • 服务端取出包序号和对应的id,然后把包序号和id对应的内容返回给客户端,同时设置返回的时间是随机的,这样就不会按照顺序返回。

粘包处理

如果我们这样发送请求:

for (let i = 0; i < 100; i++) {
  id = Math.floor(Math.random() * LESSON_IDS.length)
  socket.write(encode(id))
}
Copy after login

我们发现服务端接收到的信息如下:

<Buffer 00 00 00 02 16 64 00 01 00 02 16 68 00 02 00 02 31 1c 00 03 00 02 3c 96 00 04 00 02 16 68 00 05 00 02 16 5e 00 06 00 02 16 66 00 07 00 02 16 67 00 08 ... 550 more bytes>
Copy after login

这是因为TCP自己做的一个优化,它会把所有的请求包拼接在一起,这样就会产生粘包的现象。

服务端需要把包进行拆分,拆分成100个小包。

那如何拆分呢?

首先客户端发送的数据包包括两部分:定长的包头和不定长的包体

包头又分为两部分:包序号及包体的长度。只有知道包体的长度,才能知道从哪里进行分割。

let seq = 0
function encode(data) {
    // 正常情况下,这里应该是使用 protocol-buffers 来encode一段代表业务数据的数据包
    // 为了不要混淆重点,这个例子比较简单,就直接把课程id转buffer发送
    const body = Buffer.alloc(4);
    body.writeInt32BE(LESSON_IDS[data.id]);

    // 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分
    // 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信
    const header = Buffer.alloc(6); // 包序号占2个字节,包体长度占4个字节,共6个字节
    header.writeInt16BE(seq)
    header.writeInt32BE(body.length, 2);

    // 包头和包体拼起来发送
    const buffer = Buffer.concat([header, body])

    console.log(`包${seq}传输的课程id为${LESSON_IDS[data.id]}`);
    seq++;
    return buffer;
}

// 并发
for (let i = 0; i < 100; i++) {
    id = Math.floor(Math.random() * LESSON_IDS.length)
    socket.write(encode({ id }))
}
Copy after login

服务端进行拆包

const server = net.createServer(socket => {
  let oldBuffer = null
  socket.on(&#39;data&#39;, buffer => {
    // 把上一次data事件使用残余的buffer接上来
    if (oldBuffer) {
      buffer = Buffer.concat([oldBuffer, buffer])
    }
    let packageLength = 0
    // 只要还存在可以解成完整包的包长
    while ((packageLength = checkComplete(buffer))) {
      // 确定包的长度后进行slice分割
      const package = buffer.slice(0, packageLength)
      // 剩余的包利用循环继续分割
      buffer = buffer.slice(packageLength)

      // 把这个包解成数据和seq
      const result = decode(package)

      // 计算得到要返回的结果,并write返回
      socket.write(encode(LESSON_DATA[result.data], result.seq))
    }

    // 把残余的buffer记下来
    oldBuffer = buffer
  })
})
Copy after login

checkComplete 函数的作用来确定一个数据包的长度,然后进行分割:

function checkComplete(buffer) {
  // 如果包的长度小于6个字节说明只有包头,没有包体,那么直接返回0
  if (buffer.length <= 6) {
    return 0
  }
  // 读取包头的第二个字节,取出包体的长度
  const bodyLength = buffer.readInt32BE(2)
  // 请求包包括包头(6个字节)和包体body
  return 6 + bodyLength
}
Copy after login

decode对包进行解密:

function decode(buffer) {
  // 读取包头
  const header = buffer.slice(0, 6)
  const seq = header.readInt16BE()
    
  // 读取包体  
  // 正常情况下,这里应该是使用 protobuf 来decode一段代表业务数据的数据包
  // 为了不要混淆重点,这个例子比较简单,就直接读一个Int32即可
  const body = buffer.slice(6).readInt32BE()

  // 这里把seq和数据返回出去
  return {
    seq,
    data: body
  }
}
Copy after login

encode把客户端想要的数据转化为二进制返回,这个包同样包括包头和包体,包头又包括包需要包序号和包体的长度。

function encode(data, seq) {
  // 正常情况下,这里应该是使用 protobuf 来encode一段代表业务数据的数据包
  // 为了不要混淆重点,这个例子比较简单,就直接把课程标题转buffer返回
  const body = Buffer.from(data)

  // 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分
  // 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信
  const header = Buffer.alloc(6)
  header.writeInt16BE(seq)
  header.writeInt32BE(body.length, 2)

  const buffer = Buffer.concat([header, body])

  return buffer
}
Copy after login

当客户端收到服务端发送的包之后,同样也要进行拆包,因为所有的包同样都粘在一起了:

 <Buffer 00 00 00 00 00 1d 30 36 20 7c 20 e4 bb 80 e4 b9 88 e6 98 af e6 8a 80 e6 9c af e9 a2 84 e7 a0 94 ef bc 9f 00 01 00 00 00 1d 30 36 20 7c 20 e4 bb 80 e4 ... 539 more bytes>
Copy after login

因此,客户端也需要拆包,拆包策略与服务端的拆包策略是一致的:

let oldBuffer = null
socket.on(&#39;data&#39;, buffer => {
  // 把上一次data事件使用残余的buffer接上来
  if (oldBuffer) {
    buffer = Buffer.concat([oldBuffer, buffer])
  }
  let completeLength = 0

  // 只要还存在可以解成完整包的包长
  while ((completeLength = checkComplete(buffer))) {
    const package = buffer.slice(0, completeLength)
    buffer = buffer.slice(completeLength)

    // 把这个包解成数据和seq
    const result = decode(package)
    console.log(`包${result.seq},返回值是${result.data}`)
  }

  // 把残余的buffer记下来
  oldBuffer = buffer
})
Copy after login

到这里就实现了双全工通行,这样客户端和服务端随时都可以往对方发小消息了。

更多node相关知识,请访问:nodejs 教程

The above is the detailed content of What is RPC? Let's talk about how to implement RPC communication in node. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
1 months ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Solution to the inability to connect to the RPC server and the inability to enter the desktop Solution to the inability to connect to the RPC server and the inability to enter the desktop Feb 18, 2024 am 10:34 AM

What should I do if the RPC server is unavailable and cannot be accessed on the desktop? In recent years, computers and the Internet have penetrated into every corner of our lives. As a technology for centralized computing and resource sharing, Remote Procedure Call (RPC) plays a vital role in network communication. However, sometimes we may encounter a situation where the RPC server is unavailable, resulting in the inability to enter the desktop. This article will describe some of the possible causes of this problem and provide solutions. First, we need to understand why the RPC server is unavailable. RPC server is a

An article about memory control in Node An article about memory control in Node Apr 26, 2023 pm 05:37 PM

The Node service built based on non-blocking and event-driven has the advantage of low memory consumption and is very suitable for handling massive network requests. Under the premise of massive requests, issues related to "memory control" need to be considered. 1. V8’s garbage collection mechanism and memory limitations Js is controlled by the garbage collection machine

Detailed graphic explanation of the memory and GC of the Node V8 engine Detailed graphic explanation of the memory and GC of the Node V8 engine Mar 29, 2023 pm 06:02 PM

This article will give you an in-depth understanding of the memory and garbage collector (GC) of the NodeJS V8 engine. I hope it will be helpful to you!

Let's talk in depth about the File module in Node Let's talk in depth about the File module in Node Apr 24, 2023 pm 05:49 PM

The file module is an encapsulation of underlying file operations, such as file reading/writing/opening/closing/delete adding, etc. The biggest feature of the file module is that all methods provide two versions of **synchronous** and **asynchronous**, with Methods with the sync suffix are all synchronization methods, and those without are all heterogeneous methods.

Let's talk about the event loop in Node Let's talk about the event loop in Node Apr 11, 2023 pm 07:08 PM

The event loop is a fundamental part of Node.js and enables asynchronous programming by ensuring that the main thread is not blocked. Understanding the event loop is crucial to building efficient applications. The following article will give you an in-depth understanding of the event loop in Node. I hope it will be helpful to you!

What should I do if node cannot use npm command? What should I do if node cannot use npm command? Feb 08, 2023 am 10:09 AM

The reason why node cannot use the npm command is because the environment variables are not configured correctly. The solution is: 1. Open "System Properties"; 2. Find "Environment Variables" -> "System Variables", and then edit the environment variables; 3. Find the location of nodejs folder; 4. Click "OK".

Learn more about Buffers in Node Learn more about Buffers in Node Apr 25, 2023 pm 07:49 PM

At the beginning, JS only ran on the browser side. It was easy to process Unicode-encoded strings, but it was difficult to process binary and non-Unicode-encoded strings. And binary is the lowest level data format of the computer, video/audio/program/network package

An article to talk about how to efficiently develop presentation layer Node.js applications An article to talk about how to efficiently develop presentation layer Node.js applications Apr 17, 2023 pm 07:02 PM

How to use Node.js for front-end application development? The following article will introduce you to the method of developing front-end applications in Node, which involves the development of presentation layer applications. The solution I shared today is for simple scenarios, aiming to allow front-end developers to complete some simple server-side development tasks without having to master too much background knowledge and professional knowledge about Node.js, even if they have no coding experience.

See all articles