create account

Пишем блокчейн. Библеотека для работы с сетью. by mhuggu5hss

View this thread on: hive.blogpeakd.comecency.com
· @mhuggu5hss ·
$2.14
Пишем блокчейн. Библеотека для работы с сетью.
![blockchain-3.jpeg](https://images.hive.blog/DQmXXJGZHmUY2GZq6z4BkH26saKnTodNpHZi5oxEKYfPekX/blockchain-3.jpeg)
Теория могла показаться сложной, но к сожалению, практика ещё
сложнее. Необходимо знать программирование, чтобы воссоздать то, что было
лишь идеей. В качестве языка программирования был выбран язык Go. У
него есть ряд преимуществ над другими языками высокого уровня, как
например большая стандартная библиотека (в которую включаются
криптографические алгоритмы и функции работы с сетью) и лёгкость /
примитивность синтаксиса (что позволяет понимать его код, зная при этом
лишь язык Си). Скачать компилятор данного языка можно с официального
сайта: https://golang.org/.
К сожалению в Go нет встроенной СУБД (в качестве стандартной
библиотеки), есть лишь интерфейсы. Таким образом, необходимо установить
стороннюю открытую библиотеку, которая будет работать с СУБД SQLite3.
Это можно сделать следующим образом, при помощи терминала:
——————————————————————————————————
$ go get github.com/mattn/go-sqlite3
——————————————————————————————————
Данная СУБД является встраиваемой и не будет противоречить принципу
децентрализации, за счёт того, что каждый узел будет хранит копию блокчейна
у себя в локальной БД.
Установив всё вышеперечисленное можно приступать к написанию кода.
Сам же код будет воспроизводиться в одном лишь файле для каждой
отдельной темы, так например, библиотека для работы с сетью - один файл,
библиотека для работ с блокчейном - один файл, клиентское приложение - один
файл, смартконтракты -один файл  и приложение узла также один файл. Такой способ записи несёт как
негативные, так и положительные моменты. Так например, это негативно
сказывается на анализ всего сделанного (в готовом виде), но при этом легче
воспринимается, когда реализовываешь это всё по порядку.
Код всех программ будет преподноситься в виде схемы «причина-
следствие», то-есть начинаться с абстрактных функций, которые впоследствии
будут нести действия, исходя из логики. Так например, будет наперёд
реализовываться интерфейсная функция, а только после неё те функции,
которые находятся внутри неё. При этом также стоит сказать, что и сами
подключения пакетов будут расписываться лишь в конце глав, исходя из
реализованных ранее функций.

                       Библиотека для работы с сетью
Для создания библиотеки работающей с сетью следует использовать
стандартный пакет языка Go под названием «net». Данный пакет позволит
работать с TCP/IP соединением и на основе его будут созданы интерфейсные
функции, а также базовый протокол передачи данных.
Передача данных будет осуществляться по принципу запрос-ответ, при
этом, перед запросом будет происходить соединение, а после получения ответа
- рассоединение (аналогично HTTP протоколу).
При написании любой программы на языке Go, необходимо указывать к
какому пакету принадлежит данный исходный код. Так библиотеку,
работающую с сетью, назовём как «network», а исходный файл под названием
«network.go» расположим в директории «network/».
——————————————————————————————————
package network
——————————————————————————————————
Приступая к написанию кода надо разбраться как и какие данные будут
передаваться в разрабатываемой сети. Во-первых, необходима опция (Option),
например GET_BLNCE будет говорить о получении баланса, ADD_TRNSX о
занесении транзакции в блок и тд. Во-вторых, нужен параметр заголовка (или
сами данные), допустим при заголовке ADD_TRNSX, в качестве параметра
должна будет указываться сама транзакция, которая вносится в блок. Все эти
данные будут определены в структуре Package.

                                Структура Package.
——————————————————————————————————
type Package struct {
Option int
Data string
}
——————————————————————————————————
Имея данную структуру уже можно реализовать функцию отправки
данных Send, которая будет принимать адрес получателя и объект
структуры Package и возвращать при этом экземпляр структуры Package (ответ
от сервера).

                                Функция Send.
——————————————————————————————————
func Send(address string, pack * Package) * Package {
conn, err := net.Dial("tcp", address)
if err != nil {
return nil
}
conn.Write([]byte(SerializePackage(pack) + ENDBYTES))
var res = new(Package)
ch := make(chan bool)
go func() {
res = readPackage(conn)
ch <- true
}()
select {
case <-ch:
case <-time.After(WAITTIME * time.Second):
}
return res
}
——————————————————————————————————
Функция создаёт TCP соединение с указанием IPv4:Port адреса при
помощи функции Dial (из стандартного пакета «net»). Далее проверяется
наличие ошибки при соединении и если такая ошибка обнаруживается, тогда
возвращается нулевой адрес. Далее на экземпляр pack применяется функция
SerializePackage , которая переводит объект в строку. Полученная строка
конкатенируется с константой ENDBYTES, свидетельствующей об окончании
переданных данных . Далее при помощи метода Write полученная строка
отправляется серверу, после чего функция начинает ожидать ответа от сервера,
при помощи функции readPackage на протяжении WAITTIME  секунд.
Замечание: функции с заглавной буквы - интерфейсные (extern), с
прописной - внутренние (static). Это же правило справедливо для констант и
переменных расположенных вне функций (в глобальном пространстве).

                         Функция SerializePackage.
——————————————————————————————————
func SerializePackage(pack * Package) string {
jsonData, err := json.MarshalIndent(* pack, "", "\t")
if err != nil {
return ""
}
return string(jsonData)
}
——————————————————————————————————
Преобразует объект структуры Package в строку при помощи его
конвертации в JSON-формат. Используется функция MarshalIndent, которая
вместе с упаковкой в формате JSON, ставит табуляции во вложенных его
частях.

                          Константа ENDBYTES.
——————————————————————————————————
const (
23ENDBYTES = "\000\005\007\001\001\007\005\000"
)
——————————————————————————————————

                                Функция readPackage.
——————————————————————————————————
func readPackage(conn net.Conn) * Package {
var (
data string
size = uint64(0)
buffer = make([]byte, BUFFSIZE)
)
for {
length, err := conn.Read(buffer)
if err != nil {
return nil
}
size += uint64(length)
if size > DMAXSIZE {
return nil
}
data += string(buffer[:length])
if strings.Contains(data, ENDBYTES) {
data = strings.Split(data, ENDBYTES)[0]
break
}
}
return DeserializePackage(data)
}
——————————————————————————————————
Читает данные с сокета conn до тех пор, пока не встретит константную
строку завершения данных. Чтение происходит по порциям кратным константе
BUFFSIZE. Если произошла ошибка чтения или количество общих
переданных байт оказалось больше константы DMAXSIZE, тогда функция
возвращает нулевой адрес. Если не произошло никакой ошибки, функция
возвращает объект типа Package, используя функцию DeserializePackage
(обратную к SerializePackage).

             Константа WAITTIME, BUFFSIZE, DMAXSIZE.
——————————————————————————————————
const (
WAITTIME = 5 // seconds
DMAXSIZE = (2 << 20) // (2^20)*2 = 2MiB
BUFFSIZE = (4 << 10) // (2^10)*4 = 4KiB
)
——————————————————————————————————

                         Функция DeserializePackage.
——————————————————————————————————
func DeserializePackage(data string) *Package {
var pack Package
err := json.Unmarshal([]byte(data), &pack)
if err != nil {
return nil
}
return &pack
}
——————————————————————————————————
Теперь осталось лишь написать функцию Listen, которая будет уже
со стороны сервера (а точнее узла) прослушивать соединения. Она будет
принимать в качестве адреса строку вида IPv4:Port и функцию-обработчик.

                          Функция Listen.
——————————————————————————————————
func Listen(address string, handle func(Conn, *Package)) Listener {
splited := strings.Split(address, ":")
if len(splited) != 2 {
return nil
}
listener, err := net.Listen("tcp", "0.0.0.0:"+splited[1])
if err != nil {
return nil
}
go serve(listener, handle)
return Listener(listener)
}
——————————————————————————————————
Стоит заметить, что IPv4 адрес отбрасывается, так как в аргументах
функции net.Listen нужен лишь порт, для принятия данных. При этом в
качестве IPv4 используется шаблон (0.0.0.0), благодаря которому можно
принимать соединения с разных адресов.
Прописанная же функция Listen запускает параллельную функцию serve
, необходимую для обработки запросов, и возвращает сокет. Стоит
заметить, что возвращаемый результат оборачивается не в функцию Listener, а
преобразовывается в тип данных Listener. Это необходимо для того,
чтобы программист, импортируя сделанный нами пакет, не подключал вместе с
ним и стандартный пакет «net». Точно такая же ситуация происходит с типом
Conn, в переданной функции handle.

                                    Функция serve.
——————————————————————————————————
func serve(listener net.Listener, handle func(Conn, *Package)) {
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
break
}
go handleConn(conn, handle)
}
func handleConn(conn net.Conn, handle func(Conn, *Package)) {
defer conn.Close()
pack := readPackage(conn)
if pack == nil {
return
}
handle(Conn(conn), pack)
}
——————————————————————————————————
Принимает сокет прослушивателя и функцию-обработчик. Предназначена
для соединения с клиентом и последующего чтения данных (handleConn),
посылаемых им же, с перенаправлением на функцию-обработчик handle. После
завершения функции handle связь с клиентом будет автоматически прервана
(defer conn.Close()).

                      Тип данных Listener, Conn.
——————————————————————————————————
type Listener net.Listener
type Conn net.Conn
——————————————————————————————————
Последнее, что необходимо сделать в библиотеке, это добавить функцию
Handle. Она будет совершать действия исходя из опции принятого
пакета.

                         Функция Handle.
——————————————————————————————————
func Handle(option int, conn Conn, pack * Package, handle func(* Package) string) bool {
if pack.Option != option {
return false
}
conn.Write([]byte(SerializePackage(&Package{
Option: option,
Data: handle(pack),
}) + ENDBYTES))
return true
}
——————————————————————————————————
Принимает в качестве аргументов: 
1. опцию, на которую эта функция
привязывает исполнение, 
2. принимаемый пакет, от куда будут браться нужные
данные,
3. функцию, которая будет совершать действия исходя из данных,
возвращая при этом результат проделанной работы в виде строки. Сама же
функция Handle будет возвращать булево значение, исходя из совпадения
опций.

                  Данная библиотека использует следующие пакеты:
——————————————————————————————————
import (
"net"
"strings"
"time"
"encoding/json"
)
——————————————————————————————————
Импорты вставляются после строки, указывающей имя пакета (package
network), и перед всем остальным кодом.

Написав всю библиотеку, можно проверить корректность её исполнения
на примере простой программы отправления-принятия данных. К тому же, этот
код является неплохим шаблоном, который можно проецировать на другие
приложения.
——————————————————————————————————
package main
import (
"fmt"
"time"
"strings"
nt "./network"
)
const (
TO_UPPER = iota + 1
TO_LOWER
)
const (
ADDRESS = ":8080"
)
func main() {
var (
res = new(nt.Package)
msg = "Hello, World!"
)
go nt.Listen(ADDRESS, handleServer)
time.Sleep(500 * time.Millisecond)
// send «Hello, World!»
// receive «HELLO, WORLD!»
res = nt.Send(ADDRESS, &nt.Package{
Option: TO_UPPER,
Data: msg,
})
fmt.Println(res.Data)
// send «HELLO, WORLD!»
// receive «hello, world!»
res = nt.Send(ADDRESS, &nt.Package{
Option: TO_LOWER,
Data: res.Data,
})
fmt.Println(res.Data)
}
func handleServer(conn nt.Conn, pack *nt.Package) {
nt.Handle(TO_UPPER, conn, pack, handleToUpper)
nt.Handle(TO_LOWER, conn, pack, handleToLower)
}
func handleToUpper(pack *nt.Package) string {
return strings.ToUpper(pack.Data)
}
func handleToLower(pack *nt.Package) string {
return strings.ToLower(pack.Data)
}
——————————————————————————————————
В данном коде клиент отправляет серверу два последовательных запроса:
сначала на перевод строки в верхний регистр, а потом в нижний. Также здесь
есть такое ключевое слово как «iota», оно сигнализирует о нумерации констант
(аналогично enum в языке Си). «iota+1» говорит о том, чтобы нумерация
констант начиналась с единицы.

Назовём данный файл как «main.go». Он должен располагаться рядом с
директорией «network/».
——————————————————————————————————
network/
network.go
main.go
——————————————————————————————————
Чтобы скомпилировать и запустить этот код, необходимо прописать
следующие команды в терминале:
——————————————————————————————————
$ go build main.go
$ ./main
——————————————————————————————————
Первая команда - это компиляция, которая на основе исходного кода
main.go создаёт исполняемый (машинный) код под файлом main. Вторая
команда - исполнение.

                            Результат работы.
——————————————————————————————————
HELLO, WORLD!
hello, world!
——————————————————————————————————



---

<center><sub>[Posted Using Aeneas.Blog](https://www.aeneas.blog/@mhuggu5hss/pishem-blokchein-bibleoteka-dlya-raboty-s-setyu)</sub></center>
👍  , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
properties (23)
authormhuggu5hss
permlinkpishem-blokchein-bibleoteka-dlya-raboty-s-setyu
categoryhive-165469
json_metadata{"tags":["blockchain","aeneas","ua"],"image":["https://images.hive.blog/DQmXXJGZHmUY2GZq6z4BkH26saKnTodNpHZi5oxEKYfPekX/blockchain-3.jpeg"],"links":["https://golang.org/"],"app":"aeneas/0.1","format":"markdown","canonical_url":"https://www.aeneas.blog/@mhuggu5hss/pishem-blokchein-bibleoteka-dlya-raboty-s-setyu"}
created2021-01-09 12:33:03
last_update2021-01-09 12:33:03
depth0
children2
last_payout2021-01-16 12:33:03
cashout_time1969-12-31 23:59:59
total_payout_value1.070 HBD
curator_payout_value1.067 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length13,054
author_reputation189,036,900,051
root_title"Пишем блокчейн. Библеотека для работы с сетью."
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id101,291,221
net_rshares11,236,011,852,466
author_curate_reward""
vote details (49)
@alinalazareva · (edited)
@mhuggu5hss, мот листинги в тег _``` 

```
func Handle(option int, conn Conn, pack * Package, handle func(* Package) string) bool {
        if pack.Option != option {
return false
}
conn.Write([]byte(SerializePackage(&Package{
        Option: option,
        Data: handle(pack),
        }) + ENDBYTES))
return true
}
``````
И в них чутка табуляции??? А то ж вырвиглаз :D

ps Я ж тоже художник и тоже так вижу :P



---

<center><sub>[Posted Using Aeneas.Blog](https://www.aeneas.blog/@alinalazareva/qmpfpb)</sub></center>
properties (22)
authoralinalazareva
permlinkqmpfpb
categoryhive-165469
json_metadata{"tags":["aeneas"],"users":["mhuggu5hss"],"app":"aeneas/0.1","canonical_url":"https://www.aeneas.blog/@alinalazareva/qmpfpb","links":["https://www.aeneas.blog/@alinalazareva/qmpfpb"]}
created2021-01-10 06:07:57
last_update2021-01-10 06:09:18
depth1
children1
last_payout2021-01-17 06:07:57
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length521
author_reputation78,038,980,589,139
root_title"Пишем блокчейн. Библеотека для работы с сетью."
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id101,301,879
net_rshares0
@mhuggu5hss · (edited)
Согласен с вами, но иначе редактор блога ругается. Позднее опубликую верный репозиторий на Git.



---

<center><sub>[Posted Using Aeneas.Blog](https://www.aeneas.blog/@mhuggu5hss/qmpsos)</sub></center>
👍  
properties (23)
authormhuggu5hss
permlinkqmpsos
categoryhive-165469
json_metadata{"tags":["aeneas"],"app":"aeneas/0.1","canonical_url":"https://www.aeneas.blog/@mhuggu5hss/qmpsos","links":["https://www.aeneas.blog/@mhuggu5hss/qmpsos"]}
created2021-01-10 10:48:30
last_update2021-01-10 10:49:09
depth2
children0
last_payout2021-01-17 10:48:30
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length202
author_reputation189,036,900,051
root_title"Пишем блокчейн. Библеотека для работы с сетью."
beneficiaries
0.
accounthiveonboard
weight100
1.
accounttipu
weight100
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id101,304,378
net_rshares11,256,241,377
author_curate_reward""
vote details (1)