Blockchain — una tecnologia innovativa che promette di migliorare molti aspetti della vita umana. Essa trasferisce processi e prodotti reali nello spazio digitale, garantendo velocità e affidabilità delle operazioni finanziarie, riducendo i costi e consentendo la creazione di moderne applicazioni DAPP utilizzando contratti intelligenti in reti decentralizzate.
Considerando i numerosi vantaggi e le diverse aree di applicazione della blockchain, può sembrare strano che questa promettente tecnologia non sia ancora penetrata in tutti i settori. Il problema è che le blockchain decentralizzate attuali mancano di scalabilità. Ethereum gestisce circa 20 transazioni al secondo, il che non è sufficiente per soddisfare le esigenze del dinamico business moderno. Allo stesso tempo, le aziende che utilizzano la tecnologia blockchain non si aiutano a rinunciare a Ethereum a causa del suo elevato livello di protezione contro hackeraggi e guasti di rete.
Per garantire decentramento, sicurezza e scalabilità nella blockchain, affrontando così il Dilemma della Scalabilità, il team di sviluppatori ha creato Plasma Cash — una side chain composta da un contratto intelligente e una rete privata basata su Node.js, che trasferisce periodicamente il proprio stato nella catena principale (Ethereum).

I processi chiave in Plasma Cash
1. L'utente chiama la funzione del contratto intelligente `deposit`, passando l'importo in ETH che desidera depositare nel token Plasma Cash. La funzione del contratto intelligente crea un token e genera un evento in merito.
2. I nodi Plasma Cash, iscritti agli eventi del contratto intelligente, ricevono l'evento di creazione del deposito e aggiungono alla pool la transazione di creazione del token.
3. Periodicamente, nodi speciali di Plasma Cash prendono tutte le transazioni dalla pool (fino a 1 milione) e formano un blocco, calcolano l'albero di Merkle e, di conseguenza, l'hash. Questo blocco viene inviato ad altri nodi per la verifica. I nodi controllano se l'hash di Merkle è valido, se le transazioni sono valide (ad esempio, se il mittente del token è il suo proprietario). Dopo la verifica del blocco, il nodo chiama la funzione `submitBlock` del contratto intelligente, che salva nella catena principale il numero e l'hash di Merkle del blocco. Il contratto intelligente genera un evento di avvenuta aggiunta del blocco. Le transazioni vengono rimosse dalla pool.
4. I nodi che ricevono l'evento di invio del blocco iniziano ad applicare le transazioni che sono state aggiunte nel blocco.
5. A un certo punto, il proprietario (o non proprietario) del token desidera ritirarlo da Plasma Cash. A tale scopo, chiama la funzione `startExit`, passando le informazioni sulle ultime 2 transazioni relative al token che confermano che è lui il proprietario del token. Il contratto intelligente, utilizzando l'hash Merkle, verifica la presenza delle transazioni nei blocchi e invia il token per il prelievo, che avverrà dopo due settimane.
6. Se l'operazione di prelievo del token avviene con violazioni (il token è stato speso dopo l'inizio della procedura di prelievo o il token era già di qualcun altro prima del prelievo), il proprietario del token può contestare il prelievo entro due settimane.

La privacy è raggiunta in due modi
1. La catena principale non sa nulla delle transazioni che vengono create e inviate all'interno della catena secondaria. Rimane pubblica l'informazione su chi ha depositato e prelevato ETH in/da Plasma Cash.
2. La catena secondaria consente di organizzare transazioni anonime utilizzando zk-SNARKs.
Stack tecnologico
- NodeJS
- Redis
- Etherium
- Soild
Test
Sviluppando Plasma Cash, abbiamo testato la velocità del sistema e ottenuto i seguenti risultati:
- fino a 35.000 transazioni al secondo vengono aggiunte al pool;
- fino a 1.000.000 transazioni possono essere memorizzate in un blocco.
I test sono stati condotti su 3 seguenti server:
1. Intel Core i7-6700 Quad-Core Skylake incl. NVMe SSD — 512 GB, 64 GB DDR4 RAM
Sono stati attivati 3 nodi validatori di Plasma Cash.
2. AMD Ryzen 7 1700X Octa-Core «Summit Ridge» (Zen), SATA SSD — 500 GB, 64 GB DDR4 RAM
È stato attivato un nodo della testnet ETH Ropsten.
Sono stati attivati 3 nodi validatori di Plasma Cash.
3. Intel Core i9-9900K Octa-Core incl. NVMe SSD — 1 TB, 64 GB DDR4 RAM
È stato attivato 1 nodo per il submit di Plasma Cash.
Sono stati attivati 3 nodi validatori di Plasma Cash.
È stato avviato un test per aggiungere transazioni nella rete Plasma Cash.
In totale: 10 nodi di Plasma Cash in una rete privata.
Test 1
C'è un limite di 1 milione di transazioni in un blocco. Pertanto, 1 milione di transazioni vengono distribuite su 2 blocchi (poiché il sistema riesce a prendere parte delle transazioni e ad inviarle mentre vengono trasferite).

Stato iniziale: ultimo blocco #7; nel database sono memorizzati 1 milione di transazioni e token.
00:00 — avvio dello script di generazione delle transazioni
01:37 — creato 1 milione di transazioni e iniziato l'invio al nodo
01:46 — il nodo di submit ha preso dal pool 240k transazioni e sta formando il blocco #8. Vedi anche che nel pool vengono aggiunti 320k transazioni in 10 sec
01:58 — blocco #8 firmato e inviato per la validazione
02:03 — il blocco #8 è stato convalidato e chiamata la funzione `submitBlock` del contratto intelligente con l'hash Merkle e il numero di blocco
02:10 — il demo script ha terminato il suo lavoro, inviando 1 milione di transazioni in 32 secondi
02:33 — i nodi hanno iniziato a ricevere informazioni che il blocco #8 è stato aggiunto alla catena principale e hanno iniziato a eseguire 240k transazioni
02:40 — 240k transazioni sono state rimosse dal pool, che sono già nel blocco #8
02:56 — il nodo submit ha preso dal pool le rimanenti 760k transazioni e ha iniziato a calcolare l'hash Merkle e firmare il blocco #9
03:20 — tutti i nodi contengono 1 milione e 240k transazioni e token
03:35 — il blocco #9 è stato firmato e inviato per convalida ad altri nodi
03:41 — si è verificato un errore di rete
04:40 — il timeout ha interrotto l'attesa per la convalida del blocco #9
04:54 — il nodo submit ha preso dal pool le rimanenti 760k transazioni e ha iniziato a calcolare l'hash Merkle e firmare il blocco #9
05:32 — il blocco #9 è stato firmato e inviato per convalida ad altri nodi
05:53 — il blocco #9 è stato convalidato ed inviato alla catena principale
06:17 — i nodi hanno iniziato a ricevere informazioni che il blocco #9 è stato aggiunto alla catena principale e hanno iniziato a eseguire 760k transazioni
06:47 — il pool è stato svuotato dalle transazioni presenti nel blocco #9
09:06 — tutti i nodi contengono 2 milioni di transazioni e token
Test 2
C'è un limite di 350k per blocco. Di conseguenza, abbiamo 3 blocchi.

Stato iniziale: ultimo blocco #9; nel database sono salvati 2 milioni di transazioni e token
00:00 — lo script di generazione delle transazioni è già stato avviato
00:44 — sono state create 1 milione di transazioni e è iniziato l'invio al nodo
00:56 — il nodo submit ha preso dal pool 320k transazioni e sta formando il blocco #10. Vediamo anche che al pool sono state aggiunte 320k transazioni in 10 secondi
01:12 — il blocco #10 è stato firmato e inviato agli altri nodi per la convalida
01:18 — il demo script ha terminato il suo lavoro, inviando 1 milione di transazioni in 34 secondi
01:20 — il blocco #10 è stato convalidato ed inviato alla catena principale
01:51 — tutti i nodi hanno ricevuto dalla catena principale informazioni che il blocco #10 è stato aggiunto e iniziano a elaborare 320k transazioni
02:01 — il pool si è svuotato di 320k transazioni che sono state aggiunte al blocco #10
02:15 — il nodo submit ha preso dal pool 350k transazioni e sta formando il blocco #11
02:34 — il blocco #11 è stato firmato e inviato ad altri nodi per la convalida
02:51 — il blocco #11 è stato convalidato ed inviato alla catena principale
02:55 — l'ultimo nodo ha eseguito le transazioni dal blocco #10
10:59 — La transazione con il submit del blocco #9 è stata eseguita molto a lungo nella catena principale, ma è stata completata e tutti i nodi hanno ricevuto questa informazione e hanno iniziato a eseguire 350k transazioni.
11:05 — Il pool si è liberato di 320k transazioni, che sono state aggiunte al blocco #11.
12:10 — Tutti i nodi contengono 1 milione e 670k transazioni e token.
12:17 — Il nodo submit ha prelevato dal pool 330k transazioni e sta formando il blocco #12.
12:32 — Il blocco #12 è stato firmato e inviato agli altri nodi per la validazione.
12:39 — Il blocco #12 è stato validato e inviato alla catena principale.
13:44 — Tutti i nodi hanno ricevuto dalla catena principale l'informazione che il blocco #12 è stato aggiunto e iniziano ad applicare 330k transazioni.
14:50 — Tutti i nodi contengono 2 milioni di transazioni e token.
Test 3
Nel primo e nel secondo server, un nodo di validazione è stato sostituito con un nodo submit.

Stato iniziale: ultimo blocco #84; nella base sono salvate 0 transazioni e token.
00:00 — Sono stati avviati 3 script che generano e inviano 1 milione di transazioni ciascuno.
01:38 — Sono state create 1 milione di transazioni e è iniziato l'invio al nodo submit #3.
01:50 — Il nodo submit #3 ha prelevato dal pool 330k transazioni e sta formando il blocco #85 (f21). Inoltre, vediamo che nel pool vengono aggiunti 350k transazioni in 10 secondi.
01:53 — Sono state create 1 milione di transazioni e è iniziato l'invio al nodo submit #1.
01:50 — Il nodo submit #3 ha prelevato dal pool 330k transazioni e sta formando il blocco #85 (f21). Inoltre, vediamo che nel pool vengono aggiunti 350k transazioni in 10 secondi.
02:01 — Il nodo submit #1 ha prelevato dal pool 250k transazioni e sta formando il blocco #85 (65e).
02:06 — Il blocco #85 (f21) è stato firmato e inviato agli altri nodi per la validazione.
02:08 — Il demo-script del server #3 ha terminato il suo lavoro, inviando 1 milione di transazioni in 30 secondi.
02:14 — Il blocco #85 (f21) è stato validato e inviato alla catena principale.
02:19 — Il blocco #85 (65e) è stato firmato e inviato agli altri nodi per la validazione.
02:22 — Sono state create 1 milione di transazioni e è iniziato l'invio al nodo submit #2.
02:27 — Il blocco #85 (65e) è stato validato e inviato alla catena principale.
02:29 — Il nodo submit #2 ha prelevato dal pool 111855 transazioni e sta formando il blocco #85 (256).
02:36 — Il blocco #85 (256) è stato firmato e inviato agli altri nodi per la validazione.
02:36 — Il demo-script del server #1 ha terminato il suo lavoro, inviando 1 milione di transazioni in 42.5 secondi.
02:38 — Il blocco #85 (256) è stato validato e inviato alla catena principale.
03:08 — Il demo-script del server #2 ha terminato il suo lavoro, inviando 1 milione di transazioni in 47 secondi.
03:38 — Tutti i nodi hanno ricevuto dalla catena principale l'informazione che i blocchi #85 (f21), #86(65e), #87(256) sono stati aggiunti e iniziano ad applicare 330k, 250k, 111855 transazioni.
03:49 — il pool si è ripulito di 330k, 250k, 111855 transazioni, che sono state aggiunte ai blocchi #85 (f21), #86(65e), #87(256)
03:59 — il submit node #1 ha prelevato 888145 transazioni dal pool e sta formando il blocco #88 (214), il submit node #2 ha prelevato 750k transazioni dal pool e sta formando il blocco #88 (50a), il submit node #3 ha prelevato 670k transazioni dal pool e sta formando il blocco #88 (d3b)
04:44 — il blocco #88 (d3b) è firmato e inviato ad altri nodi per la validazione
04:58 — il blocco #88 (214) è firmato e inviato ad altri nodi per la validazione
05:11 — il blocco #88 (50a) è firmato e inviato ad altri nodi per la validazione
05:11 — il blocco #85 (d3b) è stato validato e inviato nella catena principale
05:36 — il blocco #85 (214) è stato validato e inviato nella catena principale
05:43 — tutti i nodi hanno ricevuto dalla catena principale l'informazione che i blocchi #88 (d3b), #89(214) sono stati aggiunti e iniziano ad applicare 670k, 750k transazioni
06:50 — a causa di un'interruzione della connessione, il blocco #85 (50a) non è stato validato
06:55 — il submit node #2 ha prelevato 888145 transazioni dal pool e sta formando il blocco #90 (50a)
08:14 — il blocco #90 (50a) è firmato e inviato ad altri nodi per la validazione
09:04 — il blocco #90 (50a) è stato validato e inviato nella catena principale
11:23 — tutti i nodi hanno ricevuto dalla catena principale l'informazione che il blocco #90 (50a) è stato aggiunto e iniziano ad applicare 888145 transazioni. Nel frattempo, il server #3 aveva già applicato le transazioni dai blocchi #88 (d3b), #89(214)
12:11 — tutti i pool sono vuoti
13:41 — tutti i nodi del server #3 contengono 3 milioni di transazioni e token
14:35 — tutti i nodi del server #1 contengono 3 milioni di transazioni e token
19:24 — tutti i nodi del server #2 contengono 3 milioni di transazioni e token
Ostacoli
Durante lo sviluppo di Plasma Cash, ci siamo trovati di fronte alle seguenti problematiche, che abbiamo risolto e stiamo risolvendo:
1. Conflitto tra le diverse funzioni del sistema. Ad esempio, la funzione di aggiunta delle transazioni al pool bloccava il lavoro di submit e validazione dei blocchi e viceversa, il che portava a una diminuzione della velocità.
2. Non era immediato capire come inviare un grande numero di transazioni minimizzando al contempo i costi di trasmissione dei dati.
3. Non era chiaro come e dove archiviare i dati per ottenere risultati elevati.
4. Non era chiaro come organizzare la rete tra i nodi, dato che la dimensione di un blocco con 1 milione di transazioni occupa circa 100 MB.
5. Lavorare in modalità single-thread rompe la connessione tra i nodi quando sono in corso calcoli lunghi (ad esempio, costruzione dell'albero di Merkle e calcolo del suo hash).
Come abbiamo gestito tutto questo?
La prima versione del nodo Plasma Cash era una sorta di combinazione che poteva fare tutto contemporaneamente: ricevere transazioni, sottomettere e convalidare blocchi, fornire un'API per accedere ai dati. Poiché NodeJS è originariamente monolitico, la pesante funzione di calcolo dell'albero di Merkle bloccava la funzione di aggiunta delle transazioni. Abbiamo visto due opzioni per risolvere questo problema:
1. Lanciare più processi NodeJS, ognuno dei quali esegue funzioni specifiche.
2. Utilizzare worker_threads e spostare l'esecuzione di parte del codice nei thread.
Alla fine abbiamo utilizzato entrambe le opzioni contemporaneamente: abbiamo logicamente suddiviso un nodo in 3 parti, che possono lavorare separatamente, ma allo stesso tempo in modo sincronizzato.
1. Nodo di sottomissione, che riceve le transazioni nel pool e si occupa della creazione dei blocchi.
2. Nodo di convalida, che controlla la validità dei nodi.
3. Nodo API — fornisce un'API per l'accesso ai dati.
Inoltre, è possibile collegarsi a ciascun nodo attraverso un socket unix tramite cli.
Operazioni pesanti, come il calcolo dell'albero di Merkle, le abbiamo spostate in un thread separato.
In questo modo, abbiamo raggiunto un funzionamento normale di tutte le funzioni di Plasma Cash contemporaneamente e senza errori.
Non appena il sistema ha iniziato a funzionare, abbiamo iniziato a testare la velocità e, sfortunatamente, abbiamo ottenuto risultati insoddisfacenti: 5.000 transazioni al secondo e fino a 50.000 transazioni per blocco. È stato necessario scoprire cosa fosse stato implementato in modo errato.
Per cominciare, abbiamo iniziato a testare il meccanismo di comunicazione con Plasma Cash, per scoprire le capacità massime del sistema. In precedenza avevamo scritto che il nodo Plasma Cash fornisce un'interfaccia socket unix. Inizialmente era testuale. Gli oggetti json venivano trasmessi utilizzando `JSON.parse()` e `JSON.stringify()`.
```json
{
"action": "sendTransaction",
"payload":{
"prevHash": "0x8a88cc4217745fd0b4eb161f6923235da10593be66b841d47da86b9cd95d93e0",
"prevBlock": 41,
"tokenId": "57570139642005649136210751546585740989890521125187435281313126554130572876445",
"newOwner": "0x200eabe5b26e547446ae5821622892291632d4f4",
"type": "pay",
"data": "",
"signature": "0xd1107d0c6df15e01e168e631a386363c72206cb75b233f8f3cf883134854967e1cd9b3306cc5c0ce58f0a7397ae9b2487501b56695fe3a3c90ec0f61c7ea4a721c"
}
}
```
Abbiamo misurato la velocità di invio di tali oggetti e abbiamo ottenuto circa 130k al secondo. Abbiamo provato a sostituire le funzioni standard per lavorare con json, ma le prestazioni non sono migliorate. Deve essere che il motore V8 è ben ottimizzato per queste operazioni.
Il lavoro con transazioni, token e blocchi è stato realizzato tramite classi. La creazione di tali classi ha ridotto le prestazioni di due volte, il che indica che l'OOP non è adatto a noi. Abbiamo dovuto riscrivere tutto utilizzando un approccio puramente funzionale.
Scrittura nel database
Inizialmente, per la memorizzazione dei dati, è stata scelta Redis come una delle soluzioni più performanti, soddisfacendo le nostre esigenze: storage key-value, gestione di hash table e insiemi. Abbiamo eseguito redis-benchmark e ottenuto ~80k operazioni al secondo in modalità 1 pipelining.
Per alte prestazioni, abbiamo ottimizzato Redis più nel dettaglio:
- Abbiamo configurato una connessione tramite socket unix.
- Abbiamo disattivato il salvataggio dello stato su disco (per maggior sicurezza è possibile configurare una replica e fare il salvataggio su un Redis separato).
In Redis, il pool è una tabella hash, in quanto abbiamo bisogno della possibilità di ottenere tutte le transazioni in una sola richiesta e di eliminare transazioni singolarmente. Abbiamo provato a usare una semplicissima lista, ma questa lavora più lentamente quando si scarica tutta la lista.
Utilizzando la libreria standard NodeJS per Redis, abbiamo ottenuto prestazioni di 18k transazioni al secondo. La velocità è diminuita di 9 volte.
Poiché il benchmark mostrava capacità chiaramente cinque volte superiori, abbiamo iniziato a ottimizzare. Abbiamo cambiato libreria in ioredis e ottenuto prestazioni di 25k al secondo. Le transazioni sono state aggiunte singolarmente, usando il comando `hset`. In questo modo, abbiamo generato molte richieste a Redis. È emersa l'idea di unire le transazioni in batch e inviarle con un solo comando `hmset`. Il risultato è stato 32k al secondo.
Per vari motivi, che descriveremo di seguito, lavoriamo con i dati utilizzando `Buffer` e, come si è rivelato, se convertiamo in testo (`buffer.toString(‘hex’)`) prima della scrittura, possiamo ottenere prestazioni addizionali. In questo modo, la velocità è salita a 35k al secondo. Attualmente, abbiamo deciso di sospendere ulteriori ottimizzazioni.
Abbiamo dovuto passare al protocollo binario poiché:
1. Il sistema calcola spesso hash, firme ecc., e per questo ha bisogno di dati in `Buffer`.
2. Quando vengono trasferiti tra i servizi, i dati binari occupano meno spazio rispetto al testo. Ad esempio, durante l'invio di un blocco con 1 milione di transazioni, i dati in testo possono occupare più di 300 megabyte.
3. La continua conversione dei dati influisce sulle prestazioni.
Pertanto, abbiamo basato il nostro protocollo binario per la memorizzazione e la trasmissione dei dati sul fantastico framework `binary-data`.
Di conseguenza, abbiamo ottenuto le seguenti strutture dati:
— Transazione
```json
{
prevHash: BD.types.buffer(20),
prevBlock: BD.types.uint24le,
tokenId: BD.types.string(null),
type: BD.types.uint8,
newOwner: BD.types.buffer(20),
dataLength: BD.types.uint24le,
data: BD.types.buffer(({current}) => current.dataLength),
signature: BD.types.buffer(65),
hash: BD.types.buffer(32),
blockNumber: BD.types.uint24le,
timestamp: BD.types.uint48le,
}
```
— Token
```json
{
id: BD.types.string(null),
owner: BD.types.buffer(20),
block: BD.types.uint24le,
amount: BD.types.string(null),
}
```
— Blocco
```json
{
number: BD.types.uint24le,
merkleRootHash: BD.types.buffer(32),
signature: BD.types.buffer(65),
countTx: BD.types.uint24le,
transactions: BD.types.array(Transaction.Protocol, ({current}) => current.countTx),
timestamp: BD.types.uint48le,
}
```
Con i comandi standard `BD.encode(block, Protocol).slice();` e `BD.decode(buffer, Protocol)` trasformiamo i dati in `Buffer` per salvarli in Redis o inviarli a un altro nodo e per estrarre nuovamente i dati.
Abbiamo anche 2 protocolli binari per la trasmissione dei dati tra i servizi:
— Protocollo per interagire con Plasma Node tramite unix socket
```json
{
type: BD.types.uint8,
messageId: BD.types.uint24le,
error: BD.types.uint8,
length: BD.types.uint24le,
payload: BD.types.buffer(({node}) => node.length)
}
```
dove:
- `type` — azione da eseguire, ad esempio 1 — sendTransaction, 2 — getTransaction;
- `payload` — dati da inviare alla funzione corrispondente;
- `messageId` — id del messaggio per identificare la risposta.
— Protocollo di interazione tra nodi
```json
{
code: BD.types.uint8,
versionProtocol: BD.types.uint24le,
seq: BD.types.uint8,
countChunk: BD.types.uint24le,
chunkNumber: BD.types.uint24le,
length: BD.types.uint24le,
payload: BD.types.buffer(({node}) => node.length)
}
```
dove:
- `code` — codice del messaggio, ad esempio 6 — PREPARE_NEW_BLOCK, 7 — BLOCK_VALID, 8 — BLOCK_COMMIT;
- `versionProtocol` — versione del protocollo, poiché nella rete possono essere attivate nodi con versioni diverse e possono funzionare in modi differenti;
- `seq` — identificatore del messaggio;
- `countChunk` e `chunkNumber` necessari per suddividere i messaggi di grandi dimensioni;
- `length` e `payload` lunghezza e dati stessi.
Poiché abbiamo tipizzato i dati in anticipo, il sistema finale funziona molto più velocemente rispetto alla libreria `rlp` di Ethereum. Purtroppo, non siamo ancora riusciti a rinunciare ad essa, poiché è necessario migliorare il contratto smart, cosa che prevediamo di fare in futuro.
Se siamo riusciti a raggiungere la velocità 35 000 transazioni al secondo, dobbiamo anche elaborarle in un tempo ottimale. Poiché il tempo medio di formazione di un blocco è di 30 secondi, è necessario includere nel blocco 1 000 000 transazioni, il che significa inviare più di 100 MB di dati.
Inizialmente abbiamo utilizzato la libreria `ethereumjs-devp2p` per la comunicazione tra i nodi, ma non era in grado di gestire così tanti dati. Di conseguenza, abbiamo usato la libreria `ws` e configurato l'invio di dati binari tramite websocket. Certo, ci siamo anche imbattuti in problemi nell'invio di grandi pacchetti di dati, ma li abbiamo suddivisi in chunk e ora non abbiamo più questi problemi.
Inoltre, la creazione dell'albero di Merkle e il calcolo dell'hash 1 000 000 delle transazioni richiedono circa 10 secondi di calcolo continuo. Durante questo tempo la connessione con tutti i nodi tende a cadere. È stato deciso di spostare questo calcolo in un thread separato.
Conclusioni:
In realtà, le nostre conclusioni non sono nuove, ma per qualche motivo molti specialisti le dimenticano durante lo sviluppo.
- L'uso della programmazione funzionale invece della programmazione orientata agli oggetti aumenta le prestazioni.
- Un monolite è peggiore di un'architettura basata sui servizi per un sistema performante su NodeJS.
- L'uso di `worker_threads` per calcoli intensivi migliora la reattività del sistema, soprattutto quando si lavora con operazioni I/O.
- Il socket unix è più stabile e veloce delle richieste HTTP.
- Se hai bisogno di trasmettere rapidamente grandi dati tramite rete, è meglio utilizzare i websocket e inviare dati binari suddivisi in chunk, che possono essere ripetuti se non vengono ricevuti, e poi uniti in un unico messaggio.
Ti invitiamo a visitare GitHub progetto:
L'articolo è stato scritto in collaborazione con Alexander Nashivan, sviluppatore senior di .
Fonte: habr.com
