Od czterech miesięcy pracuję nad projektem pt. „Rozwój narzędzi do ochrony i zarządzania danymi w sektorach rządowym i przemysłowym w oparciu o blockchain”.
Teraz chciałbym opowiedzieć o tym jak zacząłem ten projekt, a teraz opiszę szczegółowo kod programu.
To jest pierwszy artykuł z serii artykułów. Tutaj opisuję serwer i protokół. Tak naprawdę czytelnik może nawet napisać własne wersje tych elementów blockchain.
W zeszłym roku podczas hackatonu Digital Breakthrough wpadli na pomysł stworzenia użytecznego systemu dla przemysłu i gospodarki cyfrowej w oparciu o technologię rozproszonej księgi rachunkowej, przyznano także grant na rozwój z Funduszu Pomocy Innowacjom (powinienem napisać osobny artykuł o dotacji, dla tych, którzy dopiero zaczynają start-upy), a teraz po kolei.
Rozwój odbywa się w języku Go, a baza danych, w której przechowywane są bloki, to LevelDB.
Główne części to protokół, serwer (na którym działają TCP i WebSocket - pierwszy do synchronizacji blockchainu, drugi do łączenia klientów, wysyłania transakcji i poleceń na przykład z JavaScript).
Jak wspomniano, ten blockchain jest potrzebny przede wszystkim do automatyzacji i ochrony wymiany produktów pomiędzy dostawcami i klientami, lub obydwoma w jednej osobie. Ci ludzie nie spieszą się z zaufaniem sobie nawzajem. Ale zadaniem nie jest tylko stworzenie „książeczki czekowej” z wbudowanym kalkulatorem, ale systemu, który automatyzuje większość rutynowych zadań, które pojawiają się podczas pracy z cyklem życia produktu. Odpowiedzialny za tę sprawę kod bajtowy, jak to zwykle bywa w przypadku blockchainów, przechowywany jest na wejściach i wyjściach transakcji (same transakcje przechowywane są w blokach, bloki w LevelDB są prekodowane w formacie GOB). Najpierw porozmawiajmy o protokole i serwerze (czyli węźle).
Protokół nie jest skomplikowany, jego cała sprawa polega na tym, że w odpowiedzi na specjalną linię poleceń przechodzi w tryb ładowania niektórych danych, zwykle bloku lub transakcji, jest też potrzebny do wymiany inwentarza, aby węzeł wiedział, kogo jest podłączony i jakie mają zajęcia (węzły podłączone do sesji synchronizacji nazywane są również „sąsiadującymi”, ponieważ znany jest ich adres IP, a dane o ich stanie są przechowywane w pamięci).
Foldery (katalogi jak je nazywa Linux) w rozumieniu programistów Go nazywane są pakietami, zatem na początku każdego pliku z kodem Go z tego katalogu zapisują pakiet nazwa_folderu_gdzie_ten_plik się znajduje. W przeciwnym razie nie będziesz mógł przekazać pakietu do kompilatora. Cóż, nie jest to tajemnicą dla tych, którzy znają ten język. Oto pakiety:
- Komunikacja sieciowa (serwer, klient, protokół)
- Struktury przechowywanych i przesyłanych danych (blok, transakcja)
- Baza danych (łańcuch bloków)
- Zgoda
- Skumulowana maszyna wirtualna (xvm)
- Pomocnicze (krypto, typy) to wszystko na teraz.
Jest to wersja edukacyjna, brakuje w niej interakcji międzyprocesowych i kilku elementów eksperymentalnych, ale struktura odpowiada tej, na której prowadzony jest rozwój. Jeśli masz coś do zasugerowania w komentarzach, chętnie wezmę to pod uwagę w dalszym rozwoju. A teraz wyjaśnienie serwera i protokół.
Przyjrzyjmy się najpierw serwerowi.
Podprogram serwera działa jak serwer danych, który działa na bazie protokołu TCP, korzystając ze struktur danych z pakietu protokołu.
Procedura korzysta z następujących pakietów: serwer, protokół, typy. W samym opakowaniu tcp_server.go zawiera strukturę danych Obsługiwać.
type Serve struct {
Port string
BufSize int
ST *types.Settings
}
Może przyjąć następujące parametry:
- Port sieciowy, przez który będą wymieniane dane
- Plik konfiguracyjny serwera w formacie JSON
- Flaga do działania w trybie debugowania (prywatny blockchain)
Postęp:
- Odczytuje konfigurację z pliku JSON
- Flaga trybu debugowania jest zaznaczona: jeśli jest ustawiona, harmonogram synchronizacji sieci nie jest uruchamiany, a łańcuch bloków nie jest ładowany
- Inicjowanie struktury danych konfiguracyjnych i uruchamianie serwera
serwer
- Wykonuje uruchomienie serwera TCP i interakcję sieciową zgodnie z protokołem.
- Ma strukturę danych Serve składającą się z numeru portu, rozmiaru bufora i wskaźnika do struktury typy.Ustawienia
- Metoda Run rozpoczyna interakcję sieciową (nasłuchuje połączeń przychodzących na danym porcie, po odebraniu nowego połączenia jego przetwarzanie jest przekazywane do metody private handle w nowym wątku)
- В uchwyt dane z połączenia są wczytywane do bufora, konwertowane na reprezentację łańcuchową i przekazywane do protokół.Wybór
- protokół.Wybór zwroty dalsze lub powoduje błąd. dalsze następnie przeniesiony do protokół. Interpretacjaktóry powraca wpr - obiekt typu Interpretuj danelub powoduje błąd w przetwarzaniu wyniku selekcji
- Następnie następuje wykonanie przełącznika intrpr.Polecenia[0] który sprawdza jeden z: wynik, inw., błąd i jest odcinek domyślnym
- W dziale dalsze przełącznik jest znajdowany według wartości intrpr.Polecenia[1] który sprawdza wartości długość bufora и wersja (w każdym przypadku wywoływana jest odpowiednia funkcja)
funkcje Pobierz wersję и Długość bufora są w pliku srvlib.go pakiet serwerowy
GetVersion(conn net.Conn, version string)
po prostu drukuje na konsoli i wysyła do klienta wersję przekazaną w parametrze:
conn.Write([]byte("result:" + version))
.
Funkcja
BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)
ładuje blok, transakcję lub inne określone dane w następujący sposób:
- Wypisuje na konsolę typ danych określony w protokole, który należy zaakceptować:
fmt.Println("DataType:", intrpr.Commands[2])
- Odczytuje wartość intrpr.Ciało do zmiennej numerycznej buf_len
- Tworzy bufor nowybuf określony rozmiar:
make([]byte, buf_len)
- Wysyła poprawną odpowiedź:
conn.Write([]byte("result:ok"))
- Całkowicie wypełnia bufor ze strumienia odczytu:
io.ReadFull(conn, newbuf)
.
- Wypisuje zawartość bufora na konsoli
fmt.Println(string(newbuf))
i liczbę odczytanych bajtów
fmt.Println("Bytes length:", n)
- Wysyła poprawną odpowiedź:
conn.Write([]byte("result:ok"))
Metody z pakietu serwera są skonfigurowane tak, aby przetwarzać otrzymane dane za pomocą funkcji z pakietu protokół.
Protokół
Protokół służy jako środek reprezentujący dane w wymianie sieciowej.
Choice(str string) (str string, error) wykonuje podstawowe przetwarzanie danych otrzymanych przez serwer, otrzymuje ciąg znaków reprezentujący dane jako dane wejściowe i zwraca przygotowany ciąg znaków Interpretator:
- Ciąg wejściowy jest dzielony na głowę i ciało za pomocą ReqParseN2(str)
- head jest dzielony na elementy i umieszczany w wycinku poleceń za pomocą ReqParseHead(head)
- В przełącznik(polecenia[0]) wybierz otrzymane polecenie (cmd, klucz, adres lub sekcja została uruchomiona domyślnym)
- W cmd sprawdzane są 2 polecenia switch(commands[1]) — długość и uzyskaćwersję.
- długość sprawdza typ danych polecenia[2] i zapisuje go w typ danych
- Sprawdza to ciało zawiera wartość ciągu
len(body) < 1
- Zwraca ciąg odpowiedzi:
"result:bufferlength:" + datatype + "/" + body
- uzyskaćwersję zwraca ciąg
return "result:version/auto"
Interpretator
Zawiera strukturę InterpreteData i wykonuje wtórne przetwarzanie zwracanych danych Wybór Struny i tworzenie obiektów Interpretuj dane.
type InterpreteData struct {
Head string
Commands []string
Body string
IsErr bool
ErrCode int
ErrMessage string
}
Funkcja
Interprete(str string) (*InterpreteData, error)
akceptuje ciąg dalsze oraz tworzy i zwraca referencję do obiektu Interpretuj dane.
Postęp:
- Podobnie Wybór Głowa i ciało są wydobywane za pomocą ReqParseN2(str)
- głowa jest dzielona na elementy za pomocą ReqParseHead(głowa)
- Obiekt został zainicjowany Interpretuj dane i zwracany jest wskaźnik do niego:
res := &InterpreteData{
Head: head,
Commands: commands,
Body: body,
}
return res, nil
Obiekt ten jest używany w serwer.go pakiet główny.
klientem
Pakiet klienta zawiera funkcje Połączenie TCP и Dane odpowiedzi TCP.
Funkcja
TCPConnect(s *types.Settings, data []byte, payload []byte)
działa w następujący sposób:
- Nawiązywane jest połączenie z połączeniem określonym w przekazanym obiekcie ustawień
net.Dial("tcp", s.Host + ":" + s.Port)
- Dane przekazywane w parametrze data są przesyłane:
conn.Write(data)
- Odpowiedź została przeczytana
resp, n, _ := TCPResponseData(conn, s.BufSize)
i wydrukowane na konsoli
fmt.Println(string(resp[:n]))
- Jeśli przeniesiony ładowność potem przekazuje dalej
conn.Write(payload)
a także odczytuje odpowiedź serwera i drukuje ją na konsoli
Funkcja
TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)
tworzy bufor o określonej wielkości, odczytuje tam odpowiedź serwera i zwraca ten bufor oraz liczbę odczytanych bajtów, a także obiekt błędu.
Podprogram klienta
Służy do wysyłania poleceń do serwerów węzłów, a także uzyskiwania krótkich statystyk i testów.
Może akceptować następujące parametry: plik konfiguracyjny w formacie JSON, dane wysyłane do serwera w postaci ciągu znaków, ścieżkę do pliku wysyłanego do ładunku, flagę emulacji harmonogramu węzła, typ danych przesyłanych jako wartość liczbowa.
- Pobieranie konfiguracji
st := types.ParseConfig(*config)
- Jeśli flaga emu zostanie przekazana, rozpoczyna się sheduler
- Jeśli podana jest flaga f wskazująca ścieżkę do pliku, to ładujemy do niego jego dane fdb i treść jest wysyłana na serwer
client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
- Jeśli plik nie zostanie określony, dane z flagi zostaną po prostu wysłane -d:
client.TCPConnect(st, []byte(*data), nil)
Wszystko to jest uproszczoną reprezentacją pokazującą strukturę protokołu. W trakcie rozwoju do jego struktury dodawana jest niezbędna funkcjonalność.
W drugiej części opowiem o strukturach danych dla bloków i transakcji, w 3 o serwerze WebSocket do łączenia się z JavaScript, w 4 przyjrzę się harmonogramowi synchronizacji, następnie maszynie stosowej przetwarzającej kod bajtowy z wejść i wyjść, kryptografii i pule wyników.
Źródło: www.habr.com