Platformok közötti nyelvek közötti bináris adatkódolás, magyarázat
A Protocol Buffers egy Google által kifejlesztett eszközlánc az adatok és objektumok bináris kódolására, amely programozási nyelvek között működik. Ez az alapja a gRPC-nek, egy többnyelvű távoli eljáráshívó rendszernek, de külön is használható.
A szoftverfejlesztés története mindig is az alkalmazások közötti adatmegosztás kihívásával szembesült. Számos technika létezik, például elterjedt a szövegalapú adatformátum, például a JSON, TOML, XML vagy YAML használata. Ezeket a formátumokat a megfelelő könyvtáraknak köszönhetően a programok bármilyen programozási nyelven olvashatják vagy írhatják. Az adatok ilyen formátumú tárolása azonban több lemezterületet, az interneten keresztüli átvitel során hálózati sávszélességet, valamint a kódoláshoz és dekódoláshoz szükséges processzoridőt igényel.
Egy másik lehetőség a bináris adatformátumok használata. Például, mielőtt az internet népszerűvé vált volna, az ISO-alapú hálózati platformot széleskörű hálózatépítésre használták. Az ISO-protokollok egy bináris formátumon, az ASN.1-en alapultak, amely a sokadik fokig szigorúan meghatározott volt, és bármilyen bináris adatkódolási igényt támogatni tudott. Az ASN.1 nagyrészt elfeledett dolog, kivéve valakit, mint én, aki harminc évvel ezelőtt azon dolgozott, hogy egy ISO-protokollverem implementációt hozzon a Unix rendszerekre. Ma ez egy történelmi példa a bináris adatformátumra, amely garantált hordozhatóságot biztosít a különböző programozási nyelveken írt alkalmazások között.
A Google ezt mondja a protokollpufferekről:
A protokollpufferek nyelvsemleges, platformsemleges, bővíthető mechanizmust biztosítanak a strukturált adatok előre és visszafelé kompatibilis sorosítására. Olyan, mint a JSON, kivéve, hogy kisebb és gyorsabb, és anyanyelvi kötéseket hoz létre.
A nyelvsemleges kifejezés azt jelenti, hogy a protokollpufferek kötései állnak rendelkezésre a legnépszerűbb programozási nyelvekhez, a platformneutral pedig azt jelenti, hogy a kötések több chip architektúrához is elérhetők. A strukturált adatok kifejezés azt jelenti, hogy a protokollpufferek támogatják a sémadeklaráció szerint kódolt adatokat, és hogy a séma támogatja a definíciók egymásba ágyazását más definíciókon belül.
A protokollpufferek használata a séma .proto
fájlokkal történő leírásával kezdődik. Ezek a fájlok lehetővé teszik egy bináris adatblokk formátumának leírását, amelyet bináris üzenetpufferbe kívánnak kódolni. A .proto
fájlok kódokká fordíthatók a közel tucatnyi programozási nyelv bármelyikén. A lefordított modul kódot ad az adatok bináris formátumba való kódolásához és a bináris formátumból történő dekódoláshoz. Az Önön múlik, hogy az alkalmazás mit kezd a kódolt adatokkal.
A tervek szerint a protokollpufferként kódolt adatokat egy hálózati protokollban használják az adatok interneten keresztüli kommunikálására, innen ered a projekt neve. A Google széles körben használ protokollpuffereket például belső alkalmazásokban. De ne hagyja, hogy ez a szándékolt használat korlátozza a képzeletét.
A hivatalos dokumentáció itt található: https://developers.google.com/protocol-buffers
A cikkben látható kód a következő címen érhető el: https://github.com/robogeek/nodejs-protocol-buffers
A Node.js protokollpufferei használatának megkezdése
Ezt a Google által kifejlesztett protokollpufferek használatával kezdjük.
Az első szükség az, hogy beszerezzük a protoc
fordítót, amely a protokoll puffer definícióit kóddá konvertálja. Lehet, hogy szerencséd van, és a számítógéped csomagkezelője rendelkezik egy protokollpuffer-eszközöket tartalmazó csomaggal.
Például a macOS laptopomon MacPortokat használok nyílt forráskódú eszközök biztosítására. A protobuf3-cpp
csomag viszonylag naprakész, és azt állítja, hogy tartalmazza a fordítót.
Ubuntuban azt találtam, hogy az protobuf-compiler
tartalmazza a megfelelő eszközöket. Ezért az Ubuntu telepítése ilyen egyszerű:
$ sudo apt-get install protobuf-compiler
Ennek hiányában a https://github.com/protocolbuffers/protobuf/releases
oldalra léphet a Protocol Buffers csapat által biztosított előre elkészített csomagok lekéréséhez. Vagy letöltheti a forráskódot, és saját maga is lefordíthatja.
Ennek a gyakorlatnak a célja egy protoc
nevű parancssori eszköz, amely a protokollpufferek fordítója.
$ protoc
Usage: protoc [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given:
...
A használati üzenet akkor kerül kinyomtatásra, ha a parancs argumentumok nélkül fut le.
A Protocol Buffers specifikációs nyelvnek két változata létezik. Ebben az oktatóanyagban ennek a nyelvnek a 3-as verzióját fogjuk használni, a dokumentáció pedig on-line elérhető: https://developers.google.com/protocol-buffers/docs/proto3
A webhely sajnos nem tartalmaz oktatóanyagot a Node.js használatához. Megnézheti más nyelvek hivatalos oktatóanyagait.
A Google biztosít egy Node.js csomagot a következő címen: https://www.npmjs.com/package/google-protobuf
A csomag forrástárolója pedig nagyjából egy oktatóanyaghoz hasonló dokumentációt tartalmaz: https://github.com/protocolbuffers/protobuf-javascript/blob/main/docs/index.md
Egyszerű adatformátum meghatározása Protocol Buffers segítségével
Az oktatóanyag hátralévő részében egy egyszerű adatformátumon megyünk át, amelyet protokollpufferek és egy pár Node.js szkript határoz meg az adatok kódolásához és dekódolásához az adatformátum használatával. Ez egy egyszerű Todo objektum lesz, hasonló ahhoz, amit egy minta alkalmazásban implementáltam, amelyet az akkor újonnan kiadott Bootstrap v5 felfedezésére írtam.
Lásd: https://techsparx.com/nodejs/examples/todo-bootstrap/
Hozzon létre egy projektkönyvtárat:
$ mkdir protobuf
$ cd protobuf
$ npm init -y
$ npm install google-protobuf --save
Ehhez telepíteni kell egy külső függőséget, a protoc
fordítóprogramot, amelyet az előző részben ismertettünk.
A könyvtárban hozzon létre egy todo.proto
nevű fájlt, és tegye ezt a fájl tetejére:
syntax = "proto3";
Ez arra utasítja a protoc
-et, hogy használja a harmadik verziót. Ha ezt nem teszi meg, a fordító egy figyelmeztetést nyomtat, amely a Nincs szintaxis megadva a protofájlhoz szöveget, és felszólítja, hogy a fenti szöveget adja hozzá a fájlhoz.
A példaalkalmazásomban a TODO objektum négy mezőt tartalmaz:
- ID, amely az objektumot azonosító szám
- Egy title karakterlánc, amely a TODO listákban jelenik meg
- Egy body karakterlánc, amely akkor jelenik meg, amikor a felhasználó az egyetlen TODO elemet nézi
- Elsőbbségi felsorolás, amely magas/közepes/alacsony prioritást ad
A protokoll pufferekben a definíció így néz ki:
message Todo {
int64 id = 1;
string title = 2;
string body = 3;
Precedence precedence = 4;
}
A message
szó elindítja egy objektum meghatározását. A message
blokknak van egy neve, ebben az esetben Todo
, amely megadja az osztály nevét. A törzsben egy vagy több meződefiníció található. Minden mezőnek van adattípusa és neve. A szám-hozzárendelés a mező száma, nem pedig az alapértelmezett érték. A mező száma határozza meg, hogy az adatok hol vannak kódolva egy rekordon belül.
Az utolsó mező a Precedence típusú. Ez nincs beépítve a protokollpufferek nyelvébe, de ez az alkalmazás határozza meg:
enum Precedence { PRECEDENCE_NONE = 0; PRECEDENCE_LOW = 1; PRECEDENCE_MEDIUM = 2; PRECEDENCE_HIGH = 3; }
A enum
objektumtípust választottuk a precedence
mező megengedett értékeinek leírására. A példaalkalmazásomban az ALACSONY, KÖZEPES és MAGAS értékeket az itt látható módon határoztuk meg. Ez azt jelentette, hogy a enum
eredetileg csak ezzel a három értékkel volt megadva, és a PRECEDENCE_NONE
nem volt ott. De a fordító ezt a hibát adta:
The first enum value must be zero in proto3.
A enum
első mezőjének 0
értékkel kell rendelkeznie. A korábbi Todo alkalmazásban használt értékekkel (1, 2, 3) való kompatibilitás fenntartása érdekében ugyanazokat az értékeket szerettem volna megtartani. Ez a PRECEDENCE_NONE
0
értékű bevezetését jelentette.
A protokoll pufferek dokumentációjából kitűnik, hogy az enum deklarációk elhelyezhetők a message Todo {...}
törzsében, de a minta implementációban ez elkülönítve van.
Ez egy szinguláris objektumot, vagy úgynevezett skaláris értéktípust határoz meg. Egy tucatnyi adattípus létezik, amelyek egész számokra, lebegőpontokra, logikai értékekre és karakterláncokra vezethetők vissza. Mindegyik pontosan úgy van definiálva, hogy N bájtot vegyen fel, hogy helyesen binárisként kódolható legyen.
Egy másik megfontolandó kérdés a mezők száma. Egy alkalmazás fejlődése során előfordulhat, hogy módosítania kell az objektumdefiníciót. Felhasználhat egy meglévő mezőt egy másik típusú érték tárolására. Ez azonban tönkreteszi azokat az alkalmazásokat, amelyeket már telepített a területen. Ehelyett a legjobb gyakorlat a régebbi meződefiníciók helybenhagyása, vagy a reserved
kulcsszó használata a régebbi mezők kizárására. A cél az alkalmazás régebbi kiadásaival való visszamenőleges kompatibilitás fenntartása.
Például használhatjuk a Markdownt a body
mezőben. A meglévő body
mező egyszerű szöveget, nem Markdownt tartalmaz. Módosíthatjuk az üzenet definícióját a következőre:
message Todo {
int64 id = 1;
string title = 2;
string bodyMD = 5;
Precedence precedence = 4;
reserved 3;
}
Ez a módosítás átnevezi a body
mezőt bodyMD
-re, hogy egyértelmű legyen, hogy ez a mező tárolja a Markdownt. Ez az új mező 5
, a 3
mező száma pedig reserved
. Alternatív megoldásként hagyja a régi body
meződefiníciót, de az alkalmazás figyelmen kívül hagyja ezt a mezőt.
Az olyan skaláris értéktípusok, mint a Todo
, csak egy objektumhoz használhatók. Érdemes lehet elküldeni egy listát a Todo elemekről, ezért szükség van egy módra az objektumok tömbjének megadására.
A protokoll pufferekben ezt így írod le:
message Todos {
repeated Todo todos = 1
}
Meghatároztunk egy új objektumtípust, a Todos
. A repeated
szó most meghatározzuk, hogy mi a lényegében egy tömb. Ez azt jelenti, hogy az üzenet nulla vagy több példányt tartalmaz a Todo
objektumból, és az 1-es mezőben van elhelyezve. Nagyjából ez egy tömbbé teszi. Ez az objektum könnyen tartalmazhat más elemeket is, ha ez szükséges az alkalmazásához.
Node.js forrás előállítása a TODO protobuf sémához
Összeállítottunk egy teljes sémát. A következő lépés a forráskód átalakítása, amelyet egy alkalmazásban használhatunk. Ezért telepítettük korábban a protoc
-et.
Ez a fordító több nyelvhez is képes forráskódot generálni. BTW, ha emlékszik vissza az informatika óráira, egy fordító lefordítja a forráskódot egy programozási nyelvről egy teljesen másik nyelvre. Ezért a protoc
lefordítja a protokollpufferek forráskódját számos más programozási nyelv bármelyikére.
Ha elolvassa a Protocol Buffers dokumentációs webhelyet, akkor megvakarhatja a fejét – a Node.js-szel szeretnénk használni, de nincs dokumentáció a Node.js használatáról. Erre a Protocol Buffers csapatának meg kell válaszolnia, miért nem teszik közzé a Node.js dokumentációját a webhelyükön.
Nyissa meg a következőt: https://www.npmjs.com/package/google-protobuf
Ezután kövesse néhány linket a következő helyre való eljutáshoz: https://github.com/protocolbuffers/protobuf-javascript/blob/main/docs/index.md
Ez a két oldal a Google által készített dokumentációt tartalmazza a protokollpufferek Google implementációjának használatáról, beleértve a protoc
használatát a Node.js (JavaScript) kód generálására. Elgondolkodtató, hogy a Google miért nem terjeszti ki ezt úgy, hogy a fő webhelyen is tartalmazza a dokumentációt. Ennek oka lehet a csomag dokumentációjában szereplő figyelmeztetések, mivel a projekt állapota 2022 júliusában az, hogy a projekt némileg megszakadt, amit megpróbálnak kijavítani.
A package.json
mezőben adja hozzá ezt a script
bejegyzést:
"scripts": {
"protoc": "protoc --js_out=import_style=commonjs,binary:. todo.proto"
},
A legjobb gyakorlat, ha az ehhez hasonló parancsokat belefoglalja ebbe a fájlba, hogy ne kelljen értékes agysejteket költenie apróságokra.
A JavaScript kimenethez két stílus létezik. Az egyik a CommonJS használatával történő importálás támogatása a require
függvénnyel, ahogy a Node.js hagyományos használata, a másik pedig a Google Closure stílusú importálás. Mivel a Node.js-t célozzuk meg, commonjs
értéket adunk meg. Az binary
opció hatására a függvények binárisan szerializálódnak, és binárisból deszerializálódnak. Az opció :.
része a kimeneti könyvtárat adja meg, ebben az esetben az aktuális könyvtárat. Az :build/gen
használata azt jelenti, hogy a kimeneti könyvtár ./build/gen
. A parancs utolsó része meghatározza a bemeneti fájlt vagy fájlokat, ebben az esetben todo.proto
.
A fordító jól működik a hibaüzenetekkel. Néhányat korábban bemutattak, és könnyű volt meghatározni, mit kell tenni.
Ez a parancs egy todo_pb.js
fájlt hoz létre az aktuális könyvtárban. Tanulságos elolvasni ezt a fájlt. Látni fogja, hogy JavaScript objektumdefiníciók jönnek létre a Todo
, Todos
és Precedence
számára.
A tetején ez áll:
var jspb = require('google-protobuf');
A mi kódunk nem használja a google-protobuf
-t, hanem a generált kód. A csomagban található függvények liberális használatát a generált kódon keresztül láthatja. A csomag elérhetőségének biztosítása érdekében korábban telepítettük.
Adatok kódolása protokollpufferekhez
Egy valós alkalmazásban előfordulhat, hogy van egy kéréskezelő funkciónk, amely összegyűjt néhány adatot, és a válasz küldéséhez protokollpufferekkel kell formázni. Esetünkben szeretnénk bemutatni azt a fő lépést, hogy előállítunk egy protokollpuffer objektumot, majd sorba állítjuk egy bináris fájlba. A következő szkriptben bemutatjuk a bináris fájl deszerializálását az adatok olvasásához.
Hozzon létre egy encode.mjs
nevű fájlt (mivel az ES6 modulok jelentik a JavaScript jövőjét, lehetőség szerint ezeket használjuk). Kezdje ezzel:
import { default as Schema } from './todo_pb.js';
import { promises as fsp } from 'fs';
// console.log(Schema);
const todos = new Schema.Todos();
let todo = new Schema.Todo();
todo.setId(1);
todo.setTitle("Buy cheese");
todo.setBody("PIZZA NIGHT");
todo.setPrecedence(Schema.Precedence.PRECEDENCE_HIGH);
todos.addTodos(todo);
A generált kód CommonJS formátumú, és úgy tűnik, nincs lehetőség ES6 modul létrehozására. Ezt az importálási mintát tartották a leghasznosabbnak. A fs/promises
-et fsp
-ként is importáljuk, így aszinkron fájlrendszer-funkcióink vannak.
A generált kód lehetővé teszi a new Schema.Todos()
és new Schema.Todo()
használatát a megfelelő objektumok előállításához.
A Todo
objektumhoz nem találtam remek módot a mezőértékek beállítására. Ehelyett konkrétan meg kell hívnunk a set
metódusokat, amint az itt látható. A Todo objektum létrehozása után adja hozzá a Todos
objektumhoz a todos.addTodos
használatával.
Ismételje meg a kód utolsó bitjét, ahányszor csak akarja, tetszés szerint módosítva az értékeket. Fejezd be a szkriptet ezzel:
console.log(todos.toObject());
await fsp.writeFile('todos.bin', todos.serializeBinary());
Az első vizuális visszajelzést ad az Ön által létrehozott objektumról. A toObject
metódus a protokoll pufferek objektumát normál JavaScript objektummá alakítja.
Az utolsó sor egy fájlba írja az adatokat, todos.bin
. A serializeBinary
metódus az objektumot bináris blobbá alakítja, amely ezután a fájlba kerül.
A kimenet valahogy így fog kinézni:
{
todosList: [
{ id: 1, title: 'Buy cheese', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 2, title: 'Buy sauce', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 3, title: 'Buy Spinach', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 4, title: 'Buy ham', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 5, title: 'Buy olives', body: 'PIZZA NIGHT', precedence: 3 }
]
}
Nálunk minden szombat este a nulláról készítjük a pizzát.
Megvizsgálhatjuk a bináris fájlt is:
$ od -c todos.bin
0000000 \n 035 \b 001 022 \n B u y c h e e s e
0000020 032 \v P I Z Z A N I G H T 003 \n
0000040 034 \b 002 022 \t B u y s a u c e 032 \v
0000060 P I Z Z A N I G H T 003 \n 036 \b
0000100 003 022 \v B u y S p i n a c h 032 \v
0000120 P I Z Z A N I G H T 003 \n 032 \b
0000140 004 022 \a B u y h a m 032 \v P I Z Z
0000160 A N I G H T 003 \n 035 \b 005 022 \n B
0000200 u y o l i v e s 032 \v P I Z Z A
0000220 N I G H T 003
0000230
Nézze meg alaposan a bájtokat. Az egyes címek szövege előtt mezőszámnak tűnik, és minden egyes karakterlánc hosszának tűnik, és így tovább. Ha szeretné, a dokumentáció tartalmazza ennek a formátumnak a részletes leírását. Ezen a ponton fontos észrevenni, hogy adataink ebben a fájlban vannak.
Egy másik dolog, amit észre kell venni, a relatív méretkülönbség. A szöveges forma sokkal több bájtot vesz fel, mint a bináris forma.
A protokollpufferek deszerializálása a Node.js használatával
Mivel a protokollpufferek nyelvsemlegesek, az adatokat deszerializálhatjuk egy másik nyelven írt kód használatával. De ez arról szól, hogy ezt a Node.js-ben tegyük, ezért koncentráljunk erre.
Hozzon létre egy decode.mjs
nevű fájlt, amely tartalmazza:
import { default as Schema } from './todo_pb.js';
import { promises as fsp } from 'fs';
const todosBin = await fsp.readFile('todos.bin');
const todos = Schema.Todos.deserializeBinary(todosBin);
console.log(todos);
console.log(todos.toObject());
Ez egyszerűen beolvassa a todos.bin
értéket, és az adatokat egy objektummá deszerializálja. Ezután kinyomtatjuk magát az objektumot és a toObject
űrlapot is.
{
wrappers_: { '1': [ [Object], [Object], [Object], [Object], [Object] ] },
messageId_: undefined,
arrayIndexOffset_: -1,
array: [ [ [Array], [Array], [Array], [Array], [Array] ] ],
pivot_: 1.7976931348623157e+308,
convertedPrimitiveFields_: {}
}
{
todosList: [
{ id: 1, title: 'Buy cheese', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 2, title: 'Buy sauce', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 3, title: 'Buy Spinach', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 4, title: 'Buy ham', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 5, title: 'Buy olives', body: 'PIZZA NIGHT', precedence: 3 }
]
}
Az első bepillantást enged a protokollpuffer objektumok fedelébe. Nem kell belemélyednünk a részletekbe, de érdekes ezt látni. A második ugyanaz az adat, amit fent láttunk, jelezve, hogy sikeresen átvittük az adatokat egyik alkalmazásból a másikba.
Adatátvitel az alkalmazások között protokollpufferek használatával
Magas szinten a folyamatot a következőképpen látjuk:
- Határozza meg a sémát
.proto
fájlokban, majd állítson elő kódot az összes érdeklődő nyelvhez - Az adatok átvitele egy protokoll puffer üzenetobjektum generálásával kezdődik, majd a
serializeBinary
metódus meghívásával kezdődik - Az adatok fogadása a
deserializeBinary
metódus meghívásával történik, az üzenetpuffert protokoll puffer objektummá alakítja, majd ezeket az adatokat az alkalmazásban használja.
Alternatív protokoll pufferek megvalósítása a Node.js/JavaScript számára
A hivatalos Google protokoll pufferek megvalósítása hagy némi kívánnivalót maga után, ha a Node.js-szel használja. Az npm adattárat böngészve több más csomagot is találunk, amelyek lefedik ugyanazt a helyet. Az egyik a protocolbuf.js, amely pusztán JavaScript-implementációnak számít, TypeScript-támogatással, amely Node.js-en és böngészőkön is fut.
Két csomag van:
- A
protobufjs
futásidejű támogatást tartalmaz a protokollpufferek objektumok használatához, a sémák elemzéséhez és használatához, és még sok máshoz - A
protobufjs-cli
egy parancssori eszköz, amely nagyjából egyenértékű aprotoc
-el
A dokumentáció (https://www.npmjs.com/package/protobufjs) két használati módról beszél:
- Töltse be a
.proto
fájlokat közvetlenül fordítás nélkül, és azonnal indítsa el a metódusok hívását az objektumokon - Fordítsa le a
.proto
fájlokat statikus osztályokba, hasonlóan a fent láthatóhoz
Ebben az oktatóanyagban a második módot fogjuk használni, hogy könnyebb legyen kontrasztot adni az imént átsétált kóddal.
Telepítse a csomagokat a következő módon:
$ npm install protobufjs protobufjs-cli --save
Ez utóbbi két parancsot telepít, a pbjs
és pbts
, amelyek támogatják a JavaScript, illetve a TypeScript használatát.
Ha a .proto
fájlt használható kódra szeretné fordítani, futtassa a következőt:
$ npx pbjs -t static-module -w commonjs -o dist-pbjs/todo.js todo.proto
#### OR, for ES6 code generation
$ npx pbjs -t static-module -w es6 -o dist-pbjs/todo-es6.mjs todo.proto
Ez egy „statikus modul” létrehozását célozza meg, vagyis forráskód létrehozását. A modul formátuma CommonJS vagy ES6 formátum lesz, az Ön preferenciáitól függően. Vegye figyelembe, hogy az ES6 modult a .mjs
kiterjesztéssel neveztük el a Node.js kompatibilitás érdekében.
Hasznos megvizsgálni a generált kódot, már csak azért is, mert ez a legpraktikusabb módja a generált API megtanulásának. Hiányosnak találtam a projektdokumentációt, és a generált kód elég tiszta volt ahhoz, hogy közvetlenül megértsem a csomag használatát.
import { default as Schema } from './dist-pbjs/todo.js';
import { promises as fsp } from 'fs';
const todos = new Schema.Todos();
todos.todos.push(new Schema.Todo({
id: 1,
title: "Buy Cheese",
body: "PIZZA NIGHT",
precedence: Schema.Precedence.PRECEDENCE_HIGH
}));
// ...
console.log(Schema.Todos.toObject(todos));
await fsp.writeFile('todos-protobufjs.bin', Schema.Todos.encode(todos).finish());
Ennél a csomagnál kicsit más a használat. Például példányosíthatunk egy objektumpéldányt egy tulajdonság objektum használatával. A forráskód tanulmányozása során azt látjuk, hogy olyan tulajdonságok jönnek létre, amelyekhez közvetlenül tudunk értékeket rendelni.
A toObject
és encode
metódusok nincsenek az objektumpéldányhoz csatolva, hanem az osztály statikus metódusai. Ezért a Schema.Todos.toObject
-at hívjuk todos.toObject
helyett.
Futtassa az alkalmazást, és ezt a kimenetet látjuk a toObject
reprezentációhoz:
{
todos: [
{ id: 1, title: 'Buy Cheese', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 2, title: 'Buy sauce', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 3, title: 'Buy Spinach', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 4, title: 'Buy ham', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 5, title: 'Buy olives', body: 'PIZZA NIGHT', precedence: 3 }
]
}
Ez nagyjából ugyanaz, mint az előző. A kimeneti fájl pontosan ugyanolyan méretű, mint az előző példában:
$ ls -l todos*
-rw-rw-r-- 1 david david 152 Aug 22 11:38 todos.bin
-rw-rw-r-- 1 david david 152 Aug 22 11:37 todos-protobufjs.bin
Ez igazolja azt az elképzelést, hogy a protokollpufferek nyelvsemlegesek, mivel két különböző protokollpuffer implementációt használtunk ugyanazon fájl létrehozásához.
A decode.mjs
kis módosításával a parancssorban elnevezhetjük a fájlt, majd a következő módon dekódolhatjuk az adatfájlt:
$ node decode.mjs todos-protobufjs.bin
{
wrappers_: { '1': [ [Object], [Object], [Object], [Object], [Object] ] },
messageId_: undefined,
arrayIndexOffset_: -1,
array: [ [ [Array], [Array], [Array], [Array], [Array] ] ],
pivot_: 1.7976931348623157e+308,
convertedPrimitiveFields_: {}
}
{
todosList: [
{ id: 1, title: 'Buy Cheese', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 2, title: 'Buy sauce', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 3, title: 'Buy Spinach', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 4, title: 'Buy ham', body: 'PIZZA NIGHT', precedence: 3 },
{ id: 5, title: 'Buy olives', body: 'PIZZA NIGHT', precedence: 3 }
]
}
Ez bemutatja egy protokoll pufferfájl létrehozását az egyik implementációval, és dekódolását egy másik megvalósítással.
A protobufjs-szal írt dekóder így néz ki:
import { default as Schema } from './dist-pbjs/todo.js';
import { promises as fsp } from 'fs';
const todosBin = await fsp.readFile(process.argv[2]);
const todos = Schema.Todos.decode(todosBin);
console.log(Schema.Todos.toObject(todos).todos);
A végrehajtás (node decode-protobufjs.mjs todos-protobufjs.bin
) így néz ki:
[
{
id: Long { low: 1, high: 0, unsigned: false },
title: 'Buy Cheese',
body: 'PIZZA NIGHT',
precedence: 3
},
{
id: Long { low: 2, high: 0, unsigned: false },
title: 'Buy sauce',
body: 'PIZZA NIGHT',
precedence: 3
},
{
id: Long { low: 3, high: 0, unsigned: false },
title: 'Buy Spinach',
body: 'PIZZA NIGHT',
precedence: 3
},
{
id: Long { low: 4, high: 0, unsigned: false },
title: 'Buy ham',
body: 'PIZZA NIGHT',
precedence: 3
},
{
id: Long { low: 5, high: 0, unsigned: false },
title: 'Buy olives',
body: 'PIZZA NIGHT',
precedence: 3
}
]
Érdekes módon a id
mező másképp van ábrázolva. Ehelyett egy olyan objektum, amely alkalmasnak tűnik egy numerikus tartomány ábrázolására. Ellenkező esetben a kimenet ugyanaz, mint az előző megvalósításnál.
Benchmarking kódolás és dekódolás JSON, Protocol Buffers és Protobuf.JS használatával
Létrehoztam három benchmark függvénypárt, amelyek kizárólag a kódolási vagy dekódolási funkciót hívják meg az előre létrehozott objektumokon. JSON esetén a JSON.stringify
és JSON.parse
, a többi pedig a fent látható funkciókat használja. Az objektumtömb ebben az esetben 1000 elemből áll.
$ node bench.mjs
cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: node v18.6.0 (x64-linux)
benchmark time (avg) (min … max)
---------------------------------------------------
encode-JSON 342.37 µs/iter (311.93 µs … 1.19 ms)
decode-JSON 435.9 µs/iter (384.44 µs … 1.41 ms)
encode-PB 946.43 µs/iter (777.38 µs … 3.13 ms)
decode-PB 770.79 µs/iter (688.99 µs … 1.78 ms)
encode-PBJS 696.75 µs/iter (618.43 µs … 2.43 ms)
decode-PBJS 455.36 µs/iter (413.66 µs … 1.09 ms)
Érdekes, hogy a JSON kódolás és dekódolás lényegesen gyorsabb, mint a protokollpufferek megfelelője.
Egy másik mérőszám az egyes ábrázolások mérete:
-rw-rw-r-- 1 david david 328 Aug 22 16:46 todos.json
-rw-rw-r-- 1 david david 152 Aug 22 11:38 todos.bin
-rw-rw-r-- 1 david david 152 Aug 22 12:18 todos-protobufjs.bin
Az egyenértékű JSON több mint kétszer akkora. Nyilvánvaló, hogy a szöveg alapú adatformátum nagyobb lesz, mint egy bináris adatformátum.
Összegzés
A Google protokollpufferei hatékony módot kínálnak az alkalmazások közötti adatcserére, vagy egy alkalmazás számára az adatok tárolására. Az adatstruktúrákat kompakt bináris adatblobokként kódolja.
A protokollpufferek elsődleges előnye a kódolt adatok mérete. Mivel jól meghatározott, és több programozási nyelven is elérhető, számos alkalmazáshoz jól illeszkedik.
A szöveges adatformátumok is jól meghatározottak, és több programozási nyelven is elérhetők. Nyilvánvalóan a JSON-t, YAML-t, XML-t és hasonlókat használó alkalmazások nagy száma az adatátvitelhez bizonyítja ezek hasznosságát. De mi a helyzet azokkal az esetekkel, amikor fontos a hálózati sávszélesség megőrzése? Legyen szó néhány százezer elfoglalt szervert befogadó szerverfarmról, távolról telepített IoT-eszközről, amely 5G mobiladat-kapcsolaton keresztül csatlakozik, vagy egy okostelefon-alkalmazásról, számos forgatókönyv létezik, ahol a kompakt bináris adatok javítják a teljesítményt vagy csökkentik a hálózati adatköltségeket.
A szerzőről
„David Herron”: David Herron író és szoftvermérnök, aki a technológia bölcs használatára összpontosít. Különösen érdeklik a tiszta energiatechnológiák, mint a napenergia, a szélenergia és az elektromos autók. David közel 30 évig dolgozott a Szilícium-völgyben szoftvereken, az elektronikus levelezőrendszerektől a videó streamingen át a Java programozási nyelvig, és számos könyvet publikált a Node.js programozásról és az elektromos járművekről.
Eredetileg a https://techsparx.com címen tették közzé