P2P – komunikacja serwerów nodejs z wykorzystaniem websockets

in polish •  6 years ago 

P2P i blockchain - komunikacja przyszłości?


network-1246209_1920.jpg
Nadszedł czas na kolejną część. Tworzymy blockchain, a do tego trzeba stworzyć połączenia między wieloma serwerami. Będą kopać i przechowywać transakcje, więc muszą się łączyć i wymieniać dane. Na początek zrobimy coś prostszego. Nie ma sensu się rzucać na głęboką wodę.

Przygotujemy serwery, do których będziemy dodawać kolejne nody oraz przesyłać wiadomości w sieci. Będzie to najlepszy sposób na stworzenie prostej sieci p2p w ramach nauki. W kolejnym artykule zajmiemy się podłączeniem do blockchain, ale najpierw stwórzmy fundament.

Wykorzystam websockety do komunikacji pomiędzy nodami oraz http do zarządzania i pokazania efektów sieci.

Warto też zapoznać się z poprzednimi częściami:

  1. Historia jak poznałem blockchain i bitcoin

  2. Własny blockchain - implementacja

  3. Matematyka tworząca blockchain i bitcoin

  4. Kryptografia Blockchain, czyli dlaczego nikt nie może ukraść moich pieniędzy

  5. Experty - praktyczny przykład użycia blockchain jako smart contract

  6. Implementacja proof of work

  7. Implementacja portfela i transakcji

Oraz z innymi projektami opartymi o blockchain: Projekty krypto

HTTP - postawmy serwer


Wystawimy prosty serwer http. Będzie wyświetlał wiadomości i liczbę podłączonych nodów. Wykorzystam express, aby przyspieszyć pracę.

Zaciągamy express do kodu.

const server = require('http').createServer();
const express = require('express');
const app = express();

Następnie umieszczamy informację o liczbie podłączonych nodów oraz przesyłane wiadomości.

app.get('/', (req, res) => {
  const msg = `No. sockets ${getSockets().length}`;
  const msg1 = `${getMessages()}`;

  const whole = `${msg} ${msg1}`;
  res.send(whole);
});

Będziemy potrzebować dwóch funkcji. getSockets oraz getMessages. Będą zwracać ilość podłączonych socketów oraz wiadomości jakie są obecnie na serwerze.
Wysyłamy te informacje do '/', dzięki temu podejrzymy co się dzieje.

Dalej zrobimy proste dodawanie nowych serwerów. Wystawimy końcówkę, która będzie brała z parametrów adres innego serwera i połączy się z nim.

app.get('/add', (req, res) => {
  const addr = req.query.address;
  addNewConnection(addr);
  res.redirect('/');
});

Przyda się nam funkcja addConnection. Stworzy połączenie na websocketach. Po dodaniu połączenia wracamy na root i wyświetlamy stan.

Na koniec przesyłanie wiadomości. Musimy je jakoś dodawać.

app.get('/commit', (req, res) => {
  const msg = req.query.msg;
  addNewMessage(msg);
  res.redirect('/');
});

Pobieramy z parametrów wiadomość jaką chcemy przesłać i wykonujemy funkcję addNewMessage. Wiadomość zostanie dodana i rozesłana do wszystkich podłączonych nodów.

Na koniec tworzymy serwer p2p i http. P2P za chwilę omówimy.

initServer(parseInt(process.argv[2]) + 1);
app.listen(process.argv[2], () => console.log('Listening on http://localhost:' + process.argv[2]));

Pobieramy z argumentów port jaki chcemy użyć do nasłuchiwania. Czyli wykonując polecenie:

node index.js 1000

Odpalimy serwer http na porcie 1000, czyli adres strony będzie miał: http://localhost:1000 a websockety będą działać na porcie o jeden większym, czyli 1001.

Czas na P2P


Podsumowanie:

Potrzebujemy jeszcze:

  • Otrzymania ilości podłączonych serwerów
  • Przechowywane wiadomości
  • Dodawanie nowego połączenia
  • Dodawanie nowej wiadomości
  • Tworzenie serwera
Zacznijmy od tego jak przechowywać takie dane. Najprościej w zmiennej globalnej. Każda funkcja będzie miała do nich dostęp.
// Keep all peers connected to our server
const sockets = [];
// Keep addresses of servers
const addresses = [];

// Keep record of all messages send over the network
const messages = [];

Sockets będą trzymały nam wszystkie aktywne połączenia. Adresses zawierają adresy innych nodów a messages to wiadomości przesyłane między serwerami.

Zróbmy funkcje które przekażą na zewnątrz dane czyli:

const getSockets = () => sockets;
const getMessages = () => messages;

Czas na serwer. Potrzebne importy:

const WebSocketServer = require('ws').Server;
const WebSocket = require('ws');

oraz samo tworzenie serwera:

const initServer = (port) => {
  console.log('P2P server: ', port);
  const server = new WebSocketServer({ port });
  server.on('connection', (ws) => {
    initConnection(ws);
  });
};

Na wybranym porcie tworzymy serwer z websocketem. Jeżeli przyjdzie jakiekolwiek połączenie do niego to wykonujemy funkcję initConnection.

const initConnection = (ws, url = null) => {
  sockets.push(ws);
  console.log('Initializing...');

  handleMessages(ws);
  handleErrors(ws);
  // send information about another servers
  send(ws, { type: 'SOCKETS', addresses });
  if (url) addresses.push(url);
};

Jak widzimy trochę się tutaj dzieje. Po pierwsze dodajemy socket do listy. Potem robimy obsługę wiadomości i oddzielnie błędów. Po wszystkim wysyłamy na serwer informację z adresami jakie są do nas połączone. Na koniec dodajemy url serwera jeśli istnieje.

const handleMessages = (ws) => {
  /*
  { type: 'MESSAGE', data: string }
  { type: 'SOCKETS', addresses: array }
  */
  ws.on('message', (data) => {
    const message = JSON.parse(data);

    switch(message.type) {
      case 'MESSAGE':
        messages.push(message.data);
        console.log('New message');
        break;
      case 'SOCKETS':
        const newConnections = message.addresses.filter(add => !addresses.includes(add));
        newConnections.forEach((con) => {
          addNewConnection(con);
        });
        break;
      default:
        break;
    }
  });
};

Mamy tylko dwa typy wiadomości: MESSAGE, SOCKETS.
Wiadomość SOCKETS przekazuje nam listę adresów do których mamy się połączyć. Dzieje się to na początku działania serwera. Wybieramy wszystkie połączenia których nie mamy na liście i wykonujemy dla każdego addNewConnection.

Co do typu MESSAGE to dodaje on po prostu do naszej listy wiadomości otrzymane dane. Dzięki temu mamy na każdym serwerze te same wiadomości. Nie zawsze są ułożone chronologicznie ale zależy nam tylko na przechowywaniu ich.

const handleErrors = (ws) => {
  ws.on('close', () => removeConnection(ws));
  ws.on('error', () => removeConnection(ws));
};

Obsługa błędów jest raczej prosta i jak coś się złego dzieje to po prostu usuwamy połączenie :) Może nie jest to idealne rozwiązanie, ale w tym przypadku wystarczające.

const addNewConnection = (url) => {
  const ws = new WebSocket(url);
  console.log('Adding new connection with', url);
  ws.on('open', () => initConnection(ws, url));
  ws.on('error', () => console.log('Connection failed. Addr: ', url));
};

Dodawanie nowych połączeń opiera się na podłączeniu się do serwera przy użyciu websocketów. Używamy do tego adresu url. Po połączeniu wywołujemy znane nam już initConnection.

const removeConnection = (ws) => sockets.splice(sockets.indexOf(ws), 1);

Usuwanie połączeń opiera się na wyrzuceniu socketa z naszej listy.

const send = (ws, message) => ws.send(JSON.stringify(message));
const broadcast = (message) => sockets.forEach((socket) => send(socket, message));

Wysyłanie wiadomości to prosta funkcja. Bierzemy naszą wiadomość i zmieniamy ją na stringa. Gdy chcemy rozesłać coś do wszystkich to uruchamiamy broadcast, który iteruje po wszystkich socketach i uruchamia na nich funkcję send.

Pełen kod znajduje się na https://github.com/Patys/P2P_nodejs Zostaw gwiazdkę lub zrób forka i dodaj coś od siebie. Z chęcią omówię kod na github i potencjalne poprawki.

Podsumowanie


Mamy nasz serwer. Możemy przesyłać wiadomości. Wyświetlą się na wszystkich aktywnych nodach. To dobry start do implementacji tego w blockchain. W kolejnych postach postaramy się dodać łączność. Dalej serwer http, żeby wyświetlać stan noda.

Po tym wszystkim będziemy mieli działający blockchain oparty o dowód pracy. A to dopiero początek.

Trzymajcie się. Zamierzam podziałać mocniej w zdecentralizowanych tematach i z pewnością będziecie zadowoleni.


Bądź na bieżąco!

Wszystko wrzucam na facebook: https://www.facebook.com/patysblog/ więc zostaw like i dowiedz się pierwszy o nowym poście.

Lub wesprzyj mnie na steemit: https://steemit.com/@patys/

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Świetny artykuł. Zostawiam follow i czekam na podłączenie do blockchain. ;D

Wielkie dzięki, jak tylko dokończę, trzeba bedzie ruszyć z większymi projektami :D No i do czegoś ten stworzony blockchain wykorzystać :D

Może własne crypto? Chociaż bez większego przemyślenia i pomysłu to raczej bez sensu. Tak czy tak, zostaję na dłużej.

Wszystko będzie na blogu. Każde działanie w tym temacie opisuję artykułem :)

A masz już jakieś plany o których czytelnicy mogą wiedzieć, czy to "ściśle tajne" ? ;D

https://blog.patys.pl/2018/05/14/decyzje-i-moja-nieobecnosc/

A więcej wkrótce wrzucę na bloga, przygotowuję aplikację mobilną, kurs solidity, myślę też o stellarze i lisku. Do tego chcę się rozwijać jako programista więc myślę o kursie programowania (coś w stylu starej serii o JavaScript). Szczerze to brakuje mi tylko więcej dnia :D

O to super!

Ja chciałem zacząć naukę programowania w Android studio, ale nie mogę nigdzie poszukać żadnego dobrego i darmowego poradnika :/

Wszystko już mam przemyślane i powoli dążę do realizacji celu. Cała lista zadań jest gotowa i krok po kroku tworzę blockchain, aplikacje i inne rzeczy które będą dookoła tego :D

Congratulations @patys! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of comments received

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemitBoard - Witness Update
SteemFest³ - SteemitBoard support the Travel Reimbursement Fund.

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @patys! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of comments

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemitBoard - Witness Update

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @patys! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Support SteemitBoard's project! Vote for its witness and get one more award!