ULE — самописное MC Java ядро. Часть #1.1 — HelloWorld и изменения…

от автора

Всем привет! Уже столько времени прошло с прошлой статьи, в которой я писал про реализацию своей небольшой версии, написанной на Go, как всегда исходный код доступен на GitHub. Сразу думаю сказать, что за это время успел уже перейти на линукс(Mint Cinnamon), получить проблемы с интегрированной GPU, но в конце концов наконец я смог нормально работать с редактором от JetBrains и сделать переход с Go на Rust, это было сделано так-как я думал изначально писать на расте, но было очень проблематично компилировать… Но вот и был сделан всё-таки переход с улучшениями как производительности так и возможностей!)

Причина перехода с Go на Rust

  1. Изначально я задумывался о создании на нём, но не мог нормально скомпилировать код.

  2. Код на расте работает в разы быстрее и безопаснее.

  3. Теперь скорость можно измерять в наносекундах…)

Немного важных уточнений

При переходе на Rust я решил, что стоит делать по-умолчанию английский язык, так-как его знают большинство, а это значит если человек, который не знает русского — сможет вполне использовать, из-за этого все комментарии в статье и коде на GitHub будут написаны на английском языке, при этом может быть и много ошибок… Получилось так-же много файлов, но есть которые почти пустые и только обьединяют несколько модулей, поэтому такие файлы я не буду комментировать, а только оставлю код.

Продолжение #1.1

Код на расте вышел всё-таки в разы больше так-как я пытался использовать как можно больше своего, но всё-таки для лучшего результата использовал много, но важных библиотек и прописал их в Cargo.toml:

Содержимое Cargo.toml
[package] name = "ule" version = "0.1.0" edition = "2021" publish = true authors = [     "Distemi <distemi.bot@mail.ru>" ] homepage = "https://github.com/Distemi/ULE" repository = "https://github.com/Distemi/ULE"  [dependencies] # Быстрый HashMap и другое. ahash = "0.7.6" # Глобальные переменные lazy_static = "1.4.0" # Struct <-> JSON serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.78" # Утилиты логирования log = "0.4.14" fern = { version = "0.6", features = ["colored"] } # Время chrono = "0.4.19" # Асинхронность(скоро точно понадобиться) async-std = "1.10.0"  # Однопоточный TCP и UDP сервер [dependencies.mio] version = "0.8.0" default-features = false features = [     "os-ext",     "net" ]   [profile.release] opt-level = "z" 

Как раз наш Cargo.toml являеться одним из основных файлов для проекта, а следющий по важности src/main.rs:

Наш main.rs
#![allow(unused_must_use)] use crate::config::{ADDRESS, ADDRESS_PORT}; use crate::logger::start_input_handler; use crate::network::network_server_start; use fern::colors::Color; use std::error::Error; use std::process; use std::sync::mpsc::channel; use std::sync::Arc; use std::time::SystemTime; use std::{fmt, thread}; use utils::logger;  // Use a macros from serde(Serialize and Deserialize), log(Logging) and lazy_static(Global variables) #[macro_use] extern crate serde; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static;  pub mod config; pub mod network; pub mod utils;  // Main function of application fn main() {     let start = SystemTime::now();     // Initialize logger     println!("Starting ULE v1.0.0...");     if let Err(err) = logger::setup_logger() {         eprintln!("Failed to initialize logger: {}", err);         process::exit(1);     }     // Creating channel for multithreading communication with main's thread and network's thread     let (tx, rx) = channel::<bool>();     // Generate server's address and make it accessible with thread safe     let address = Arc::new(String::from(format!(         "{}:{}",         ADDRESS,         ADDRESS_PORT.to_string()     )));     // Start network in another thread     thread::spawn({         let address = address.to_string();         move || {             // Start network             // If failed to start when return error             if let Err(err) = network_server_start(address, &tx) {                 error!("{}", err);                 tx.send(false);             }         }     });     // Wait for status from server's network     if rx.recv().unwrap_or(false) {         // If Server successful started         info!("Server started at {}", address);         // Showing about the full launch and showing the time to start         {             let elapsed = start.elapsed().unwrap();             info!(                 "The server was successfully started in {}",                 if elapsed.as_secs() >= 1 {                     format!("{}s", elapsed.as_secs())                 } else if elapsed.as_millis() >= 1 {                     format!("{}ms", elapsed.as_millis())                 } else {                     format!("{}ns", elapsed.as_nanos())                 }             );             drop(elapsed);         };     } else {         // If Failed to start Server         error!("Failed to start server on {}.", address);         process::exit(1);     }     // Remove channel     std::mem::drop(rx);     // Start console input handler(input commands)     start_input_handler(); }  // Custom error(yes, not std::io:Error) #[derive(Debug)] pub struct SimpleError(String, Option<std::io::Error>);  impl Error for SimpleError {}  impl fmt::Display for SimpleError {     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {         // Check is error provided         if self.1.is_some() {             write!(f, "{}: {:?}", self.0, self.1)         } else {             write!(f, "{}", self.0)         }     } }  // Custom Result with custom Error pub type SResult<T> = Result<T, SimpleError>; 

В нашем 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, так-как они зависимы:

src/utils/mod.rs
pub mod chat; pub mod logger;

Просто импортируем модули чата и логгера публично

src/utils/logger/mod.rs
mod input; mod log_lib;  pub use {input::start_input_handler, log_lib::setup_logger};

Тут идёт импорт input.rs и log_lib.rs, а так-же экспорт методов start_input_handler и setup_logger

Содержимое файла с методом инициализации логгера
use crate::Color; use fern::colors::ColoredLevelConfig; use std::fs;  // Logger's initialize(fern, color and log) pub fn setup_logger() -> Result<(), fern::InitError> {     // Removing latest log if exists     fs::remove_file("latest.log");     // Setting colors     let colors = ColoredLevelConfig::new()         .info(Color::BrightBlack)         .warn(Color::Yellow)         .error(Color::Red)         .trace(Color::BrightRed);     // Setting fern     fern::Dispatch::new()         // Setting custom format to logging         .format(move |out, message, record| {             out.finish(format_args!(                 "{} [{}] {}",                 chrono::Local::now().format("[%m-%d %H:%M:%S]"),                 colors.color(record.level()),                 message             ))         })         // Setting log-level         .level(log::LevelFilter::Info)         // Setting target's logger         .chain(std::io::stdout())         // Setting log's file         .chain(fern::log_file("latest.log")?)         // Applying settings         .apply()?;     // If successful setting - returning ok     Ok(()) }

При инициализации логера мы в первую очередь удаляем файл последнего лога latest.log, потом устанавливаем на каждый уровень лога свой цвет(INFO = серый, WARN — жёлтый, ERROR — красный, TRACE — ярко-красный). Позже идёт инициализация самого логгера fern и для него мы устанавливаем формат: [ДАТА] [УРОВЕНЬ] СООБЩЕНИЕ, цвет имеет только уровень, а дата и сообщение стандартным цвветом консоли. Для логгера устанавливаем вывод в stdout(консоль вывода), минимальный уровень вывода — INFO, а так-же вывод в лог-файл и принимаем эти изменения. Если не было ошибок при этих действиях — возращяем успешный пустой результат.

Далее у нас через main.rs создаётся канал mpsc, который передаётся в другой поток сетевого сервера TCP и это делается через network_server_start из пакета network, который имеет много «пустых» файлов, но оттуда нам нужен лишь протокол, сервер, буферы и обработчики. Сам сетевой сервер располагается по пути src/network/server.rs:

Содержимое сетевого сервера
use crate::network::handler::{handshaking, status_handler}; use crate::network::network_client::ConnectionType::HANDSHAKING; use crate::network::network_client::NetworkClient; use ahash::AHashMap; use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; use std::io; use std::sync::mpsc::Sender; use std::sync::Mutex; use std::time::Duration;  // Declare global variables lazy_static! {     // Server need to shut down? (true - yes, needs to shutdown network server).     pub static ref SHUTDOWN_SERVER: Mutex<bool> = Mutex::new(false);     // Server's works status.     pub static ref NET_SERVER_WORKS: Mutex<bool> = Mutex::new(true); }  // Server's Token(ID) const SERVER: Token = Token(0);  // Next Token fn next(current: &mut Token) -> Token {     let next = current.0;     current.0 += 1;     Token(next) }  // Start a network server pub fn network_server_start(address: String, tx: &Sender<bool>) -> std::io::Result<()> {     // Creating Network Pool     let mut poll = Poll::new()?;     // Creating Network Events Pool     let mut events = Events::with_capacity(256);     // Converting String's address to SocketAddr     let addr = address.parse().unwrap();     // Starting a Network Listener     let mut server = TcpListener::bind(addr)?;     // Register server's Token     poll.registry()         .register(&mut server, SERVER, Interest::READABLE)?;      // Creating a list of connections     let mut connections: AHashMap<Token, NetworkClient> = AHashMap::new();     // Creating a variable with latest token.     let mut unique_token = Token(SERVER.0 + 1);     // Send over the channel that the server has been successfully started     tx.send(true);      // Network Events getting timeout     let timeout = Some(Duration::from_millis(10));     // Infinity loop(while true) to handing events     loop {         // Checks whether it is necessary to shutdown the network server         if *SHUTDOWN_SERVER.lock().unwrap() {             *NET_SERVER_WORKS.lock().unwrap() = false;             info!("Network Server Stopped!");             return Ok(());         }         // Getting a events from pool to event's pool with timeout         poll.poll(&mut events, timeout)?;         // Handing a events         for event in events.iter() {             // Handing event by token             match event.token() {                 // If it server's event                 // Reading a all incoming connection                 SERVER => loop {                     // Accepting connection                     let (mut connection, _) = match server.accept() {                         // If successful                         Ok(v) => v,                         // If not exists incoming connection                         Err(e) if e.kind() == io::ErrorKind::WouldBlock => {                             break;                         }                         // If failed to get incoming connection                         Err(e) => {                             return Err(e);                         }                     };                      // Generating new token for this connection                     let token = next(&mut unique_token);                     // Registering connection with token                     poll.registry().register(                         &mut connection,                         token,                         Interest::READABLE.add(Interest::WRITABLE),                     )?;                     // Pushing connection into connection's list                     connections.insert(                         token,                         NetworkClient {                             stream: connection,                             conn_type: HANDSHAKING,                         },                     );                 },                 // Handing event from client                 token => {                     // Handing event by connection's stage                     let done = if let Some(connection) = connections.get_mut(&token) {                         let m = match &connection.conn_type {                             HANDSHAKING => handshaking,                             _ => status_handler,                         };                         // Trying to handing                         m(connection, &event).unwrap_or(false)                     } else {                         false                     };                     // If needs to close connection - removing from list, unregister and close connection's stream                     if done {                         if let Some(mut connection) = connections.remove(&token) {                             poll.registry().deregister(&mut connection.stream)?;                             connections.remove(&token);                         }                     }                 }             }         }     } }

Да, уже целых 125 строчек, но это ещё мало

В нём мы инициализируем глобальные переменные используя lazy_static, обратите внимание, что тип bool завёрнут в оболочку Mutex, который гарантирует мультипоточный доступ к переменной, к чтении и записи, но для получения этих переменных блокируется поток ожидая информации. Создаётся так-же простая примитивная структура SERVER, который имеет значение айди сервера в сетевом сервере. Далее идёт next, который работает как ++ для переменных(в расте не ++, а +=1), а следует за этой функцией уже другая — network_server_start. Функция сетевого сервера на старте инициализирует два пула, один отвечает за хранилища событий и дальше идёт парсинг строки в адресс, а следом попытка запустить TCP сервер на этом адрессе, который регестрируем в пуле как только-чтение, после мы создаём список подключений и уникальный токен на новое подключение, если не было ошибок, то отправляем в канал связи — true, который означать о успешном запуске. Переменная timeout используется как лимит ожидания событий, чтобы начать цикл обработки запросов заного, что есть в цикле: проверка на нужду в отключении сервера и если надо, то просто устанавливаем статус, что сервер выключен и останавливаем цикл, остальную работу по одключении слушателя и тд делаем сам компилятор. Если же останавливать нам не надо, то мы ждём до 10мс сетевые события и позже обрабатываем существующие события. Серверные события бывают только — принятие нового подключения, поэтому мы создаём ещё цикл в котором принимаем все подключения, регистрируем их. В случае если это события связанные с клиентами(присланный пакет например) — в зависимости от типа подключения(HandShaking, Status и тд) передаём соответствующему обработчику и в случае если обработчик возращает true, то мы разрываем соединение и удаляем его из хранилища подключений.

Протокол, обработка подключений, чтение и создание пакетов

Так-как ядро я пытаюсь сделать менее зависимым от библиотек, то для чата и протокола будет всё написано с 0 и для обеспечения большего контроля над чтением и записью.

Было решено хранить буферы пакетов в виде векторов к которым добавлены методы чтения и записи(только для Vec<u8>):

Чтение буферов
use crate::{SResult, SimpleError};  /// Reader [Vec] of bytes pub trait PacketReader {     // 1-Byte     fn get_u8(&mut self) -> u8;     fn get_i8(&mut self) -> i8;     // 2-Byte     fn get_u16(&mut self) -> u16;     fn get_i16(&mut self) -> i16;     // 4-Byte     fn get_varint(&mut self) -> SResult<i32>;     // 8-Byte     fn get_i64(&mut self) -> i64;     // Another     fn get_string(&mut self) -> SResult<String>;     fn read_base(&mut self) -> SResult<(i32, i32)>; }  // Apply reader to Vec impl PacketReader for Vec<u8> {     // Read a single byte as u8 ( 8-Bit Unsigned Integer )     fn get_u8(&mut self) -> u8 {         self.remove(0)     }      // Read a single byte as i8 ( 8-Bit Integer )     fn get_i8(&mut self) -> i8 {         self.remove(1) as i8     }      // Read a two bytes as u16 ( 16-Bit Unsigned Integer )     fn get_u16(&mut self) -> u16 {         u16::from_be_bytes([self.get_u8(), self.get_u8()])     }      // Read a two bytes as i16 ( 16-Bit Integer )     fn get_i16(&mut self) -> i16 {         i16::from_be_bytes([self.get_u8(), self.get_u8()])     }      // Read a VarInt ( Dynamic-length 32-Bit Integer )     fn get_varint(&mut self) -> SResult<i32> {         // Result variable         let mut ans = 0;         // Read up to 4 bytes         for i in 0..4 {             // Read one byte             let buf = self.get_u8();             // Calculate res with bit moving and another             ans |= ((buf & 0b0111_1111) as i32) << 7 * i;             // If it's limit when stop reading             if buf & 0b1000_0000 == 0 {                 break;             }         }         // Return result as successful         Ok(ans)     }      // Read a Long ( 64-Bit Integer )     fn get_i64(&mut self) -> i64 {         // Yes, read 8 bytes         i64::from_be_bytes([             self.get_u8(),             self.get_u8(),             self.get_u8(),             self.get_u8(),             self.get_u8(),             self.get_u8(),             self.get_u8(),             self.get_u8(),         ])     }      // Read a String ( VarInt as len; bytes[::len] )     fn get_string(&mut self) -> SResult<String> {         // Getting string-length         let len = self.get_varint()?;         // Create String's bytes buffer         let mut buf = Vec::new();         // Reading Bytes         for _ in 0..len {             buf.push(self.get_u8())         }         // Convert Bytes to UTF8 String         match String::from_utf8(buf) {             Ok(v) => Ok(v),             Err(_) => Err(SimpleError(String::from("Failed to parse chars"), None)),         }     }     // Read first two VarInt(Packet's length and id)     fn read_base(&mut self) -> SResult<(i32, i32)> {         let len = self.get_varint()?;         let pid = self.get_varint()?;         Ok((len, pid))     } }

Тут мы можем наглядно увидеть чтение VarInt, String, Long и другое, что пока надо было при написании ядра.

Запись в буферы пакетов
/// Writer [Vec] of bytes pub trait PacketWriter {     // 1-Byte     fn write_u8(&mut self, value: u8);     fn write_i8(&mut self, value: i8);     // 2-Byte     fn write_u16(&mut self, value: u16);     fn write_i16(&mut self, value: i16);     // 4-Byte     fn write_varint(&mut self, value: i32);     // 8-Byte     fn write_i64(&mut self, value: i64);     // Another     fn write_vec_bytes(&mut self, bytes: Vec<u8>);     fn write_string(&mut self, value: String);     fn create_packet(&mut self, pid: i32) -> Vec<u8>; }  impl PacketWriter for Vec<u8> {     // Writing byte     fn write_u8(&mut self, value: u8) {         self.push(value);     }      // Writing byte     fn write_i8(&mut self, value: i8) {         self.push(value as u8)     }      // Writing 2-byte unsigned integer     fn write_u16(&mut self, value: u16) {         self.extend_from_slice(&value.to_be_bytes());     }      // Writing 2-byte unsigned integer     fn write_i16(&mut self, value: i16) {         self.extend_from_slice(&value.to_be_bytes());     }      // Writing bytes as VarInt     fn write_varint(&mut self, mut value: i32) {         // Bytes buffer         let mut buf = vec![0u8; 1];         // Byte's length         let mut n = 0;         // Converts value to bytes         loop {             // Break if it's limit             if value <= 127 || n >= 8 {                 break;             }             // Pushing a byte to buffer             buf.insert(n, (0x80 | (value & 0x7F)) as u8);             // Moving value's bits on 7             value >>= 7;             value -= 1;             n += 1;         }         // Pushing byte, because it lower that 256(<256)         buf.insert(n, value as u8);         n += 1;         // Pushing converted bytes into byte's buffer         self.extend_from_slice(&buf.as_slice()[..n])     }      // Writing Long ( 64-Bit Integer )     fn write_i64(&mut self, value: i64) {         self.extend_from_slice(value.to_be_bytes().as_slice())     }      // Alias of extend_from_slice, but works with Vec, not Slice     fn write_vec_bytes(&mut self, mut bytes: Vec<u8>) {         self.append(&mut bytes);     }      // Write String (VarInt as len and string's bytes)     fn write_string(&mut self, value: String) {         // Getting String as Bytes         let bytes = value.as_bytes();         // Writing to buffer a length as VarInt         self.write_varint(bytes.len() as i32);         // Writing to buffer a string's bytes         self.extend_from_slice(bytes);     }      // Packet's base builder     fn create_packet(&mut self, pid: i32) -> Vec<u8> {         // Creating empty packet's buffer         let mut packet = Vec::new();         // Creating length's bytes buffer and fill it as VarInt         let mut len_bytes: Vec<u8> = Vec::new();         len_bytes.write_varint(pid);         // Writing full packet's length(content + length's bytes)         packet.write_varint((self.len() + len_bytes.len()) as i32);         // Writing length bytes         packet.extend_from_slice(len_bytes.as_slice());         // Drop(Free) length bytes buffer         drop(len_bytes);         // Writing some packet's content         packet.extend_from_slice(self.as_slice());         // Returning result         packet     } }

Запись к буферам выглядит в разы интересней из-за больших требований к стандарту протокола MineCraft.

Обработчики пакетов на статусы 0(HandShaking) и 1(Status) расположены в одном файле src/network/handler.rs и в нём на каждый тип своя функция.

Вот например HandShaking:

pub fn handshaking(conn: &mut NetworkClient, event: &Event) -> SResult<bool> {     // Checking if we can read the package     if !event.is_readable() {         return Ok(false);     }     // Reading packet     let handshake = read_handshake_packet(conn);     // Checking if is error     if handshake.is_err() {         return Ok(true);     }     // Getting results     let (_, _, _, next_state) = handshake.unwrap();     // Change types     conn.conn_type = match next_state {         1 => STATUS,         _ => STATUS,     };     Ok(false) }

Хоть функция и имеет 20 строчек, но в ней мы требуем лишь чтения первого пакета для определения следующего статуса ну и основное чтение пакета происходит в read_handshake_packet:

Функция чтения HandShake
pub fn read_handshake_packet(client: &mut NetworkClient) -> SResult<(u32, String, u16, u32)> {     // Read bytes from client     let (ok, p, err) = match client.read() {         Ok((ok, p)) => (ok, Some(p), None),         Err(err) => (false, None, Some(err)),     };     // If failed to read when...     if !ok || err.is_some() {         return Err(SimpleError(             String::from("Failed to read packet"),             if err.is_some() { err.unwrap().1 } else { None },         ));     }     // Reading packet     let mut p: Vec<u8> = p.unwrap();     // Try to read Length and PacketID from packet(on handshaking stage only 0x00)     p.read_base()?;     // Reading version, address and etc.     let ver = p.get_varint()? as u32;     let address = p.get_string()?;     let port = p.get_u16();     let next_state = p.get_varint()? as u32;     // States can be only 1 - status, 2 - play     if next_state >= 3 {         return Err(SimpleError(String::from("Invalid client"), None));     }     // Returning results     Ok((ver, address, port, next_state)) }

Мы читаем пакет и при ошибке возращаем её, а если получилось прочитать полностью, то и возращаем результаты чтения.

Для обработки статуса у нас есть иная функция:

pub fn status_handler(conn: &mut NetworkClient, event: &Event) -> SResult<bool> {     // Checking if we can read and write     if !event.is_readable() || !event.is_writable() {         return Ok(false);     }     // Getting a input's bytes     let (ok, p, err) = match conn.read() {         Ok((ok, p)) => (ok, Some(p), None),         Err(err) => (false, None, Some(err)),     };     // Checking if a read or not     if !ok {         return Ok(err.is_some());     }     // Packet's bytes     let mut p: Vec<u8> = p.unwrap();     // Cloning bytes(for ping-pong)     let bytes = p.clone();     // Reading a packet's length(and remove...) and PacketID     let (_, pid) = p.read_base()?;     match pid {         // Is Ping List         0x00 => {             drop(bytes);             conn.stream.write_all(&*create_server_list_ping_response());         }         // Is Ping-Pong         0x01 => {             conn.stream.write_all(bytes.as_slice());             match conn.stream.peer_addr() {                 Ok(v) => info!("Server pinged from {}", v),                 Err(_) => {                     info!("Server pinged.")                 }             }         }         _ => {}     }     Ok(false) }

В ней снова при вызове в первую очередь проверяем на возможность не только чтения, но и записи так-как на этом этапе мы всегда отдаём некий результат. Снова читаем буффер и пытаемся прочитать его начал сохранив при этом айди пакета(0x00 — Список, 0x01 — Пинг-Понг) я так-же реализовал небольшой генератор буффера для списка:

Сам генератор ответа на список
// Structs for status MOTD response #[derive(Debug, Serialize)] pub struct ListPingResponse {     pub version: ListPingResponseVersion,     pub players: ListPingResponsePlayers,     pub description: ChatMessage, }  #[derive(Debug, Serialize)] pub struct ListPingResponseVersion {     pub name: String,     pub protocol: u32, }  #[derive(Debug, Serialize)] pub struct ListPingResponsePlayers {     pub max: u32,     pub online: u32,     pub sample: Vec<ListPingResponsePlayerSample>, }  #[derive(Debug, Serialize)] pub struct ListPingResponsePlayerSample {     pub name: String,     pub id: String, } /// Build packet's bytes as result pub fn create_server_list_ping_response() -> Vec<u8> {     // Initialize empty byte's vector     let mut bytes = Vec::new();     // Generating String and convert to bytes.     // String generated as JSON by serde and serde_json libraries     bytes.write_string(         serde_json::to_string(&ListPingResponse {             version: ListPingResponseVersion {                 name: String::from("ULE"),                 protocol: PROTOCOL_VERSION,             },             players: ListPingResponsePlayers {                 max: 10,                 online: 0,                 sample: vec![],             },             // Some clients can read colors and so on without convert into JSON             description: ChatMessage::str("&a&lHello!"),         })         .unwrap(),     );     // Build completed packet. Server List Ping - PacketID is 0x00     bytes.create_packet(0x00) }

Для него мы сначала используем информацию о том как должен выглядеть JSON ответа и для ответа мы делаем пустой буффер, записываем байты переведённой сструктуры в JSON и генерируем пакет с айди 0x00. Для серелизации используем serde и serde_json.

Если пингануть сервер, то можно увидеть будет результат. При получении пинг-понг мы просто отправляем копию буфера так-как это более экономный вариант так-как иначе бы пришлось читать Long и другое, что нагружало бы процессор и ОЗУ больше чем просто копия буффера.

Последнее… Input в консоли или же STDIN.

Последняя функция из main.rs — ввод комманд, пока он будет очень примитивный и иметь всего stop и вывод введённого. Так-как имеется не так много возможностей казалось бы, то и ничего важного не будет, но нет! Он будет нам блокировать основной поток приложения возволяя ему работать, ведь если основной поток будет остановлен, то и вся программа основиться. Поэтому как выглядит функция так:

use crate::network::{NET_SERVER_WORKS, SHUTDOWN_SERVER}; use std::time::Duration; use std::{io, process, thread};  // Loop for handling input pub fn start_input_handler() -> std::io::Result<()> {     // Input buffer     let mut inp = String::new();     // STDIN - os input     let stdin = io::stdin();     // loop for infinity handling     loop {         // Before write buffer we need to clear buffer         inp.clear();         // Reading a line         stdin.read_line(&mut inp)?;         // Clearing input's buffer         inp = inp.replace("\n", "");         // Simple realisation of stop command, but in updates be removed from here in another place         if inp.starts_with("stop") {             // Sending status to shutdown network server             *SHUTDOWN_SERVER.lock().unwrap() = true;             info!("Stopping server...");             // Running process killing in 6 secs if failed to common shutdown             thread::spawn(|| {                 thread::sleep(Duration::from_secs(6));                 process::exit(0);             });             // Waiting for shutdown network's server             loop {                 if *NET_SERVER_WORKS.lock().unwrap() == true {                     thread::sleep(Duration::from_millis(25));                 } else {                     break;                 }             }             // Disabling the input             return Ok(());         }         // If it's not stop command - when display buffer, but in updates be removed         info!("Entered: {}", inp);     } }

Мы сначала создаём буффер и stdin, а так-же запускаем цикл в котором сначала очищаем буффер от прошлого ввода и потом блокируем поток в ожидании ввода. При получении ввода проверяем на содержмиое и если это stop, то устанавливаем сетевому серверу информацию о том, что надо выключать слушатель и дожидаемся выключения, но если за 6 секунд не произошло отключения — завершаем процесс с кодом 0, если же у нас была введена иная комманда, то просто выводим её в консоль с уровнем INFO, да, просто выводим.

Итог части #1.1

Данная часть была как-бы заменой #1 и в данной конечно из важного было:

  1. Переход на Rust с языка Go

  2. Основной язык проекта — Английский

  3. Улучшение производительности в разы благодаря Rust

  4. Создание логгера и ввода

  5. Полная работа ядра пока в 2 потоках, а не как выходило в Go

Вот такие изменения я думаю стоили такого перехода, тем более учитывая, что Rust мне больше нравиться благодаря своей приближённости к устройству и отличная работа с ОЗУ:

Использование ресурсов при активном пинге
Использование ресурсов при активном пинге
Результат при пинге сервера. Почти тоже самое, что и в прошлой части.
Результат при пинге сервера. Почти тоже самое, что и в прошлой части.

Сервер пингуется легко, потребляя 128КБ на Linux, а на моём 4300U запускался за 735236ns.


Я надеюсь вам интересно читать статьи о разработке ядра и снова скажу:

Исходный код ядра доступен на GitHub и если вы хотите поддержать меня валютой, то у меня есть patreon.

Напишите можалуйста ваше мнение о моём процессе. Буду стараться отвечать на все.

Взаранее скажу, что плагины скоро буду вводить и они будут на основе WASM, скорее всего благодаря движку Wasmer.


ссылка на оригинал статьи https://habr.com/ru/post/649779/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *