Cet article examine et compare les méthodes/formats de sérialisation et désérialisation des données : JSON, Buffers (protocole binaire personnalisé), Protobuf et MessagePack, et propose des conseils sur la façon de les implémenter. . (Repère de performance à la fin)
La méthode d'envoi de messages la plus courante est JSON. Où vous encodez des données dans une chaîne afin qu'elles puissent être transmises via un message Websocket et les analyser.
ws.send(JSON.stringify({greeting: "Hello World"]})) ws.on("message", (message) => { const data = JSON.parse(message); console.log(data) })
Un protocole binaire personnalisé est une implémentation personnalisée légère de la sérialisation et de la désérialisation des données. Il est couramment utilisé lorsque la vitesse, les performances et la faible latence sont cruciales, par ex. jeux multijoueurs en ligne et plus (ou si vous souhaitez optimiser votre application). Lors de la création d'un protocole binaire personnalisé, vous travaillez avec des tampons et du binaire, ce qui peut être difficile à mettre en œuvre, mais si vous avez des connaissances en tampons et en binaire, cela ne devrait poser aucun problème.
const encoder = new TextEncoder(); const decoder = new TextDecoder(); function binary(text, num) { const messageBytes = encoder.encode(text); // array buffers cannot store strings, so you must encode // the text first into an array of binary values // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33] const buffer = new ArrayBuffer(1 + messageBytes.length); // when creating array buffers, //you must predetermine the size of your array buffer const view = new DataView(buffer); // create a view to interact with the buffer view.setUint8(0, num); const byteArray = new Uint8Array(buffer); byteArray.set(messageBytes, 1); return buffer; } ws.on("open", () => { const msg = binary("Hello World!", 123); ws.send(msg); }) ws.on("message", (message) => { const buffer = message.buffer; const view = new DataView(buffer); const num = view.getUint8(0); const textLength = buffer.byteLength - 1 const textBytes = new Uint8Array(buffer, 1, textLength); const text = decoder.decode(textBytes); console.log(text, num); });
Cette fonction sérialise deux propriétés, l'une étant du texte et l'autre étant un nombre dans un tampon de tableau.
Dans cet exemple de code, j'utilise protobuf.js, une implémentation javascript de protobufs. J'utilise la réflexion pour générer le code protobuf au moment de l'exécution. Vous pouvez également générer du code de manière statique, mais cela n'a aucun impact sur les performances selon le wiki protobuf.js, cependant, il charge le code protobuf plus rapidement, mais cela n'a aucun impact sur les performances lors de l'envoi de messages websocket.
syntax = "proto3"; message TestMessage { string text = 1; uint32 num = 2; }
import protobuf from "protobufjs"; const ws = new Websocket("ws://localhost:3000"); ws.binaryType = "arraybuffer"; protobuf.load("testmessage.proto", function (err, root) { if (err) throw err; if (root === undefined) return; const TestMessage = root.lookupType("TestMessage") ws.on("open", () => { const message = TestMessage.create({text: "Hello World!", num: 12345}); const buffer = TestMessage.encode(message).finish(); ws.send(buffer); }); ws.on("message", (msg) => { const buffer = new Uint8Array(msg); const data = TestMessage.decode(buffer); console.log(data.text); console.log(data.num); }); })
import { encode, decode } from "@msgpack/msgpack"; ws.binaryType = "arraybuffer" ws.on("open", () => { const msg = encode({"Hello World!", 123}); ws.send(msg); }) ws.on("message", (msg) => { const data = decode(msg); console.log(data); })
Pour comparer les performances de chaque format/méthode de sérialisation de données, j'ai écrit un benchmark qui mesure les performances lors de l'envoi de données via des Websockets.
J'ai divisé les benchmarks en différents groupes.
Petite saisie de données
Saisie de données moyenne
Saisie Big Data
Il s'agit de mesurer les performances de ces sérialisations de données sur différentes tailles de données. J'ai également enregistré les performances de la sérialisation, de la désérialisation et de la durée totale pour chaque groupe. J'ai effectué les benchmarks exacts 5 fois pour chaque groupe et calculé la moyenne pour garantir la fiabilité de ces tests.
Le benchmark envoie des messages Websocket en 100 000 itérations. Le code est écrit en Bun.js
Ces repères ont été enregistrés dans le temps pour terminer (ms), donc plus petit est plus rapide.
ws.send(JSON.stringify({greeting: "Hello World"]})) ws.on("message", (message) => { const data = JSON.parse(message); console.log(data) })
Method | Byte size (bytes) |
---|---|
JSON | 33 |
Custom Binary Protocol | 13 |
Protobuf | 17 |
MessagePack | 24 |
const encoder = new TextEncoder(); const decoder = new TextDecoder(); function binary(text, num) { const messageBytes = encoder.encode(text); // array buffers cannot store strings, so you must encode // the text first into an array of binary values // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33] const buffer = new ArrayBuffer(1 + messageBytes.length); // when creating array buffers, //you must predetermine the size of your array buffer const view = new DataView(buffer); // create a view to interact with the buffer view.setUint8(0, num); const byteArray = new Uint8Array(buffer); byteArray.set(messageBytes, 1); return buffer; } ws.on("open", () => { const msg = binary("Hello World!", 123); ws.send(msg); }) ws.on("message", (message) => { const buffer = message.buffer; const view = new DataView(buffer); const num = view.getUint8(0); const textLength = buffer.byteLength - 1 const textBytes = new Uint8Array(buffer, 1, textLength); const text = decoder.decode(textBytes); console.log(text, num); });
Method | Byte size (bytes) |
---|---|
JSON | 117 |
Custom Binary Protocol | 70 |
Protobuf | 75 |
MessagePack | 102 |
syntax = "proto3"; message TestMessage { string text = 1; uint32 num = 2; }
Method | Byte size (bytes) |
---|---|
JSON | 329 |
Custom Binary Protocol | 220 |
Protobuf | 229 |
MessagePack | 277 |
MessagePack a soudainement cessé de fonctionner après environ 6 600 messages envoyés.
Dans tous les benchmarks, le protocole binaire personnalisé est le plus rapide dans le temps total et a la taille d'octet la plus petite/la plus efficace lors de la sérialisation messages. Cependant, les différences de performances sont significatives.
Étonnamment, le temps de sérialisation de JSONest considérablement plus rapide que la sérialisation du Protocole binaire personnalisé. C'est probablement parce que JSON.stringify() est implémenté en c natif avec Node et en zig natif avec Bun. Les résultats peuvent également varier lors de l'utilisation de Node, car JSON.stringify() avec Bun est 3,5 fois plus rapide que Node.
MessagePack pourrait potentiellement être plus rapide car dans ce benchmark, j'ai utilisé l'implémentation javascript officielle de MessagePack. Il existe d'autres implémentations de MessagePack potentiellement plus rapides, telles que MessagePackr.
Merci d'avoir lu !
Benchmark (écrit en dactylographié) : https://github.com/nate10j/buffer-vs-json-websocket-benchmark.git
Voir les résultats ici dans les feuilles Google.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!