Всем привет! Уже столько времени прошло с прошлой статьи, в которой я писал про реализацию своей небольшой версии, написанной на Go, как всегда исходный код доступен на GitHub. Сразу думаю сказать, что за это время успел уже перейти на линукс(Mint Cinnamon), получить проблемы с интегрированной GPU, но в конце концов наконец я смог нормально работать с редактором от JetBrains и сделать переход с Go на Rust, это было сделано так-как я думал изначально писать на расте, но было очень проблематично компилировать... Но вот и был сделан всё-таки переход с улучшениями как производительности так и возможностей!) Изначально я задумывался о создании на нём, но не мог нормально скомпилировать код. Код на расте работает в разы быстрее и безопаснее. Теперь скорость можно измерять в наносекундах...) При переходе на Rust я решил, что стоит делать по-умолчанию английский язык, так-как его знают большинство, а это значит если человек, который не знает русского - сможет вполне использовать, из-за этого все комментарии в статье и коде на GitHub будут написаны на английском языке, при этом может быть и много ошибок... Получилось так-же много файлов, но есть которые почти пустые и только обьединяют несколько модулей, поэтому такие файлы я не буду комментировать, а только оставлю код. Код на расте вышел всё-таки в разы больше так-как я пытался использовать как можно больше своего, но всё-таки для лучшего результата использовал много, но важных библиотек и прописал их в Cargo.toml: Как раз наш Cargo.toml является одним из основных файлов для проекта, а следющий по важности src/main.rs: В нашем main.rs в самом начале инициализируем отсчёт времени, который вскоре будем использовать для показа время запуска. Потом мы пытаемся инициализировать логгер(fern + log), при неудаче - выводим ошибку и "убиваем" процесс. Следующим шагом у нас идёт создания некого канала, а после строки адресса сервера, но канал, который на самом деле выглядит по методам как UDP, но с блокировкой потоков, он нам нужен для ожидания основного потока, который ждёт результат запуска от потока сетевого сервера(TCP сервер), получилось - выводим информацию об успешном запуске сетевого сервера и за сколько времени запустилось, при ошибке - выводим информацию о проблеме запуска. Если получилось запустить наш TCP сервер, то удаляем наш канал связи и начинаем слушать ввод с консоли. Как видно есть типы SResul(из сокращения SimpleResult) и SimpleError, первый говорит сам за себя, как и второй, но для которого идёт приминение разных trait для показа ошибок. Наш метод инициализации логгера лежит в файле src/utils/logger/input.rs, но я покажу так-же src/utils/mod.rs и src/utils/logger/mod.rs, так-как они зависимы: Просто импортируем модули чата и логгера публично Тут идёт импорт input.rs и log_lib.rs, а так-же экспорт методов start_input_handler и setup_logger При инициализации логера мы в первую очередь удаляем файл последнего лога latest.log, потом устанавливаем на каждый уровень лога свой цвет(INFO = серый, WARN - жёлтый, ERROR - красный, TRACE - ярко-красный). Позже идёт инициализация самого логгера fern и для него мы устанавливаем формат: [ДАТА] [УРОВЕНЬ] СООБЩЕНИЕ, цвет имеет только уровень, а дата и сообщение стандартным цвветом консоли. Для логгера устанавливаем вывод в stdout(консоль вывода), минимальный уровень вывода - INFO, а так-же вывод в лог-файл и принимаем эти изменения. Если не было ошибок при этих действиях - возращяем успешный пустой результат. Далее у нас через main.rs создаётся канал mpsc, который передаётся в другой поток сетевого сервера TCP и это делается через network_server_start из пакета network, который имеет много "пустых" файлов, но оттуда нам нужен лишь протокол, сервер, буферы и обработчики. Сам сетевой сервер располагается по пути src/network/server.rs:  Да, уже целых 125 строчек, но это ещё мало В нём мы инициализируем глобальные переменные используя lazy_static, обратите внимание, что тип bool завёрнут в оболочку Mutex, который гарантирует мультипоточный доступ к переменной, к чтении и записи, но для получения этих переменных блокируется поток ожидая информации. Создаётся так-же простая примитивная структура SERVER, который имеет значение айди сервера в сетевом сервере. Далее идёт next, который работает как ++ для переменных(в расте не ++, а +=1), а следует за этой функцией уже другая - network_server_start. Функция сетевого сервера на старте инициализирует два пула, один отвечает за хранилища событий и дальше идёт парсинг строки в адресс, а следом попытка запустить TCP сервер на этом адрессе, который регестрируем в пуле как только-чтение, после мы создаём список подключений и уникальный токен на новое подключение, если не было ошибок, то отправляем в канал связи - true, который означать о успешном запуске. Переменная timeout используется как лимит ожидания событий, чтобы начать цикл обработки запросов заного, что есть в цикле: проверка на нужду в отключении сервера и если надо, то просто устанавливаем статус, что сервер выключен и останавливаем цикл, остальную работу по одключении слушателя и тд делаем сам компилятор. Если же останавливать нам не надо, то мы ждём до 10мс сетевые события и позже обрабатываем существующие события. Серверные события бывают только - принятие нового подключения, поэтому мы создаём ещё цикл в котором принимаем все подключения, регистрируем их. В случае если это события связанные с клиентами(присланный пакет например) - в зависимости от типа подключения(HandShaking, Status и тд) передаём соответствующему обработчику и в случае если обработчик возращает true, то мы разрываем соединение и удаляем его из хранилища подключений. Так-как ядро я пытаюсь сделать менее зависимым от библиотек, то для чата и протокола будет всё написано с 0 и для обеспечения большего контроля над чтением и записью. Было решено хранить буферы пакетов в виде векторов к которым добавлены методы чтения и записи(только для Vec<u8>): Тут мы можем наглядно увидеть чтение VarInt, String, Long и другое, что пока надо было при написании ядра. Запись к буферам выглядит в разы интересней из-за больших требований к стандарту протокола MineCraft. Обработчики пакетов на статусы 0(HandShaking) и 1(Status) расположены в одном файле src/network/handler.rs и в нём на каждый тип своя функция. Вот например HandShaking: Хоть функция и имеет 20 строчек, но в ней мы требуем лишь чтения первого пакета для определения следующего статуса ну и основное чтение пакета происходит в read_handshake_packet: Мы читаем пакет и при ошибке возращаем её, а если получилось прочитать полностью, то и возращаем результаты чтения. Для обработки статуса у нас есть иная функция: В ней снова при вызове в первую очередь проверяем на возможность не только чтения, но и записи так-как на этом этапе мы всегда отдаём некий результат. Снова читаем буффер и пытаемся прочитать его начал сохранив при этом айди пакета(0x00 - Список, 0x01 - Пинг-Понг) я так-же реализовал небольшой генератор буффера для списка: Для него мы сначала используем информацию о том как должен выглядеть JSON ответа и для ответа мы делаем пустой буффер, записываем байты переведённой сструктуры в JSON и генерируем пакет с айди 0x00. Для серелизации используем serde и serde_json. Если пингануть сервер, то можно увидеть будет результат. При получении пинг-понг мы просто отправляем копию буфера так-как это более экономный вариант так-как иначе бы пришлось читать Long и другое, что нагружало бы процессор и ОЗУ больше чем просто копия буффера. Последняя функция из main.rs - ввод комманд, пока он будет очень примитивный и иметь всего stop и вывод введённого. Так-как имеется не так много возможностей казалось бы, то и ничего важного не будет, но нет! Он будет нам блокировать основной поток приложения возволяя ему работать, ведь если основной поток будет остановлен, то и вся программа остановится. Поэтому как выглядит функция так: Мы сначала создаём буффер и stdin, а так-же запускаем цикл в котором сначала очищаем буффер от прошлого ввода и потом блокируем поток в ожидании ввода. При получении ввода проверяем на содержмиое и если это stop, то устанавливаем сетевому серверу информацию о том, что надо выключать слушатель и дожидаемся выключения, но если за 6 секунд не произошло отключения - завершаем процесс с кодом 0, если же у нас была введена иная комманда, то просто выводим её в консоль с уровнем INFO, да, просто выводим. Данная часть была как-бы заменой #1 и в данной конечно из важного было: Переход на Rust с языка Go Основной язык проекта - Английский Улучшение производительности в разы благодаря Rust Создание логгера и ввода Полная работа ядра пока в 2 потоках, а не как выходило в Go Вот такие изменения я думаю стоили такого перехода, тем более учитывая, что Rust мне больше нравиться благодаря своей приближённости к устройству и отличная работа с ОЗУ: Сервер пингуется легко, потребляя 128КБ на Linux, а на моём 4300U запускался за 735236ns. Я надеюсь вам интересно читать статьи о разработке ядра и снова скажу: Исходный код ядра доступен на GitHub и если вы хотите поддержать меня валютой, то у меня есть patreon.  Напишите можалуйста ваше мнение о моём процессе. Буду стараться отвечать на все. Взаранее скажу, что плагины скоро буду вводить и они будут на основе WASM, скорее всего благодаря движку Wasmer. Разработчик сайтов и приложений