久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合

站長資訊網
最全最豐富的資訊網站

什么是RPC?聊聊node中怎么實現 RPC 通信

什么是RPC?聊聊node中怎么實現 RPC 通信

node.js極速入門課程:進入學習

【相關教程推薦: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,所以有更小的數據體積。

因為傳輸的是二進制,更適合于計算機來理解,文本協議更適合人類理解,所以計算機去解讀各個字段的耗時是比文本協議少很多的。

RPC采用二進制有更小的數據體積,及更快的解讀速度。

  • 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) })
登錄后復制

以上半雙工通信步驟如下:

  • 客戶端發送消息 socket.write(buffer)
  • 服務端接受消息后往客戶端發送消息 socket.write(buffer)
  • 客戶端接受消息后再次發送消息

這樣在一個時間端之內,只有一個端往另一個端發送消息,這樣就實現了半雙工通信。那如何實現全雙工通信呢,也就是在客戶端往服務端發送消息的同時,服務端還沒有消息返回給客戶端之前,客戶端又發送了一個消息給服務端。

全雙工通信

先來看一個場景:

什么是RPC?聊聊node中怎么實現 RPC 通信

客戶端發送了一個id1的請求,但是服務端還來不及返回,接著客戶端又發送了一個id2的請求。

等了一個之后,服務端先把id2的結果返回了,然后再把id1的結果返回。

那如何結果匹配到對應的請求上呢?

如果按照時間順序,那么id1的請求對應了id2的結果,因為id2是先返回的;id2的請求對應了id1的結果,這樣就導致請求包和返回包錯位的情況。

怎么辦呢?

我們可以給請求包和返回包都帶上序號,這樣就能對應上。

錯位處理

客戶端代碼:

socket.on('data', 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)
登錄后復制

服務端代碼:

const server = net.createServer(socket => {   socket.on('data', 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)   }) })
登錄后復制

  • 客戶端把包序號和對應的id給服務端
  • 服務端取出包序號和對應的id,然后把包序號和id對應的內容返回給客戶端,同時設置返回的時間是隨機的,這樣就不會按照順序返回。

粘包處理

如果我們這樣發送請求:

for (let i = 0; i < 100; i++) {   id = Math.floor(Math.random() * LESSON_IDS.length)   socket.write(encode(id)) }
登錄后復制

我們發現服務端接收到的信息如下:

<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>
登錄后復制

這是因為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 })) }
登錄后復制

服務端進行拆包

const server = net.createServer(socket => {   let oldBuffer = null   socket.on('data', 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   }) })
登錄后復制

checkComplete 函數的作用來確定一個數據包的長度,然后進行分割:

function checkComplete(buffer) {   // 如果包的長度小于6個字節說明只有包頭,沒有包體,那么直接返回0   if (buffer.length <= 6) {     return 0   }   // 讀取包頭的第二個字節,取出包體的長度   const bodyLength = buffer.readInt32BE(2)   // 請求包包括包頭(6個字節)和包體body   return 6 + bodyLength }
登錄后復制

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   } }
登錄后復制

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 }
登錄后復制

當客戶端收到服務端發送的包之后,同樣也要進行拆包,因為所有的包同樣都粘在一起了:

 <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>
登錄后復制

因此,客戶端也需要拆包,拆包策略與服務端的拆包策略是一致的:

let oldBuffer = null socket.on('data', 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 })
登錄后復制

到這里就實現了雙全工通行,這樣客戶端和服務端隨時都可以往對方發小消息了。

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
久久精品五月,日韩不卡视频在线观看,国产精品videossex久久发布 ,久久av综合
国产激情在线播放| 日产欧产美韩系列久久99| 国产亚洲福利| 黄色成人在线网址| 欧美日韩视频一区二区三区| 亚洲a一区二区三区| 日本精品不卡| 日本精品影院| 欧美1级日本1级| 激情自拍一区| 久久xxxx| 欧美日韩18| 日本va欧美va欧美va精品| 国产欧美欧美| 四虎4545www国产精品| 国产精品99一区二区| 欧美日韩免费观看一区=区三区 | 国产精成人品2018| 国产成人精选| 久久中文亚洲字幕| 综合五月婷婷| 国产欧美一区二区三区国产幕精品| 久久亚洲国产精品尤物| 欧美.日韩.国产.一区.二区| 亚洲三级网址| 中文字幕一区久| 视频一区二区欧美| 美女久久久精品| 亚洲精品888| 欧美aⅴ一区二区三区视频| 成人羞羞在线观看网站| 激情欧美日韩一区| 国产精品va视频| 91成人网在线观看| 麻豆精品在线视频| 国产精品美女久久久浪潮软件| 人人爱人人干婷婷丁香亚洲| 吉吉日韩欧美| 青青草91视频| 国产女优一区| 精品亚洲美女网站| 日韩国产成人精品| 久久国产中文字幕| 国内精品麻豆美女在线播放视频| 午夜在线精品偷拍| 欧洲av一区二区| 欧美日韩 国产精品| 午夜在线视频观看日韩17c| 麻豆视频在线观看免费网站黄| 日本aⅴ亚洲精品中文乱码| 久久久久久久久久久9不雅视频| 国产精品一区二区中文字幕| 日韩影院在线观看| 日韩一级精品| 99精品国产一区二区三区| 精品视频在线观看网站| 久久精品99久久久| 日本综合精品一区| 日韩欧美中文字幕一区二区三区 | 日韩高清不卡一区二区| 欧美福利一区| 91久久在线| 在线亚洲观看| 亚洲一区二区三区四区五区午夜| 精品捆绑调教一区二区三区| 四虎成人av| 精品成人免费一区二区在线播放| 日韩av片子| 性欧美videohd高精| 欧洲精品一区二区三区| 91亚洲自偷观看高清| 欧美一级精品| 7m精品国产导航在线| 在线一区免费观看| 蜜臀av在线播放一区二区三区| 日本精品不卡| 久久亚洲欧美| 欧美日韩亚洲一区在线观看| 欧美一级一区| 精品三级av| 久久久久久久久久久9不雅视频| 米奇777超碰欧美日韩亚洲| 亚洲精品中文字幕乱码| 蜜臀久久久久久久| 国产精品草草| 五月天久久久| 日韩不卡一区二区三区| 美女免费视频一区| 亚洲精品一区二区在线看| 丝袜诱惑制服诱惑色一区在线观看| 狠狠久久婷婷| 国产精品香蕉| 亚洲一级影院| 国产人成精品一区二区三| 福利精品一区| 一区二区亚洲视频| 日韩成人精品一区二区| 中文字幕日韩亚洲| 在线亚洲人成| 日本va欧美va精品| 亚洲一级影院| 国产a亚洲精品| 日韩精品欧美精品| 蜜桃视频欧美| 精品一区不卡| 欧美专区在线| 日韩精品欧美| 精品视频91| 亚洲啊v在线免费视频| 亚洲91视频| 欧美少妇精品| 亚洲日本欧美| 日韩国产一区| 国产欧美亚洲精品a| 国产精品亚洲二区| 日韩一区二区免费看| 高清在线一区| 国产日韩亚洲欧美精品| 国产精品试看| 99亚洲视频| 美女福利一区二区三区| 日韩午夜视频在线| 亚洲一区日本| 精品一区亚洲| 欧美va天堂在线| 久久久久网站| 999国产精品永久免费视频app| 国产精品久久久久9999高清| 日韩欧美在线精品| 日本不卡高清视频| 日韩精品免费视频人成| 色8久久久久| 日日摸夜夜添夜夜添国产精品| 免费日韩av片| 中文在线日韩| 日韩一区二区三区免费视频| 亚洲精一区二区三区| 日本国产亚洲| 国产精品男女| 成人污污视频| 在线日韩一区| 一区二区三区午夜视频| 欧美另类中文字幕| 久久只有精品| 91精品国产乱码久久久久久久| 国内精品福利| 欧美精品国产| 亚洲www免费| 亚洲另类av| 荡女精品导航| 久久久久99| 亚洲欧美网站在线观看| 国产伦理久久久久久妇女| 成人在线丰满少妇av| 在线亚洲免费| 国产精品男女| 欧美一级专区| 国产成人久久精品一区二区三区| 欧美国产91| 鲁大师精品99久久久| 亚洲午夜视频| 麻豆精品蜜桃视频网站| 午夜欧美精品| 麻豆精品新av中文字幕| 夜夜嗨网站十八久久 | 国产精品毛片久久| 国产亚洲欧洲| 欧洲精品一区二区三区| 亚州国产精品| 亚洲性图久久| 91亚洲自偷观看高清| 亚洲网址在线观看| 九九精品调教| 精品国产精品国产偷麻豆| 亚洲图片久久| 欧美日韩国产一区精品一区| 国产一区不卡| 欧美日韩一区二区三区四区在线观看 | 久久久男人天堂| 青草国产精品| 免费在线观看视频一区| 日韩不卡视频在线观看| 久久一区精品| 国产精品亚洲欧美日韩一区在线 | 国产精品极品在线观看| 亚洲综合日本| 激情婷婷久久| 欧美日韩国产传媒| 欧美sss在线视频| 精品欠久久久中文字幕加勒比| 69堂精品视频在线播放| 一级成人国产| 蜜桃免费网站一区二区三区| 野花国产精品入口| 国产精品丝袜xxxxxxx| 香蕉成人久久| 蜜桃久久久久久久| 鲁大师成人一区二区三区| 国产尤物精品| 视频一区在线视频|