listeners and handlers

This commit is contained in:
MeexReay 2025-05-01 21:13:47 +03:00
parent 103b8314f7
commit f8684a0402
6 changed files with 261 additions and 57 deletions

16
Cargo.lock generated
View File

@ -129,6 +129,12 @@ dependencies = [
"serde",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
@ -221,6 +227,15 @@ dependencies = [
"serde",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -323,6 +338,7 @@ dependencies = [
name = "rust_minecraft_server"
version = "0.1.0"
dependencies = [
"itertools",
"rust_mc_proto",
"serde",
"serde_default",

View File

@ -10,3 +10,4 @@ serde_json = "1.0.140"
serde_with = { version = "3.12.0", features = ["macros"] }
serde_default = "0.2.0"
toml = "0.8.22"
itertools = "0.14.0"

View File

@ -13,8 +13,8 @@ pub struct ServerConfig {
#[serde(default = "default_timeout")] pub timeout: u64,
}
fn default_host() -> String { return "127.0.0.1:25565".to_string(); }
fn default_timeout() -> u64 { return 5; }
fn default_host() -> String { "127.0.0.1:25565".to_string() }
fn default_timeout() -> u64 { 5 }
impl ServerConfig {
pub fn load_from_file(path: PathBuf) -> Option<ServerConfig> {

118
src/context.rs Normal file
View File

@ -0,0 +1,118 @@
use std::{net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicI32, AtomicU16, Ordering}, Arc, RwLock, RwLockWriteGuard}};
use itertools::Itertools;
use rust_mc_proto::{MinecraftConnection, Packet};
use crate::{config::ServerConfig, data::ServerError};
pub struct ServerContext {
pub config: Arc<ServerConfig>,
listeners: Vec<Box<dyn Listener>>,
handlers: Vec<Box<dyn PacketHandler>>
}
impl ServerContext {
pub fn new(config: Arc<ServerConfig>) -> ServerContext {
ServerContext {
config,
listeners: Vec::new(),
handlers: Vec::new()
}
}
pub fn add_packet_handler(&mut self, handler: Box<dyn PacketHandler>) {
self.handlers.push(handler);
}
pub fn add_listener(&mut self, listener: Box<dyn Listener>) {
self.listeners.push(listener);
}
pub fn packet_handlers<F, K>(
self: &Arc<Self>,
sort_by: F
) -> Vec<&Box<dyn PacketHandler>>
where
K: Ord,
F: FnMut(&&Box<dyn PacketHandler>) -> K
{
self.handlers.iter().sorted_by_key(sort_by).collect_vec()
}
pub fn listeners<F, K>(
self: &Arc<Self>,
sort_by: F
) -> Vec<&Box<dyn Listener>>
where
K: Ord,
F: FnMut(&&Box<dyn Listener>) -> K
{
self.listeners.iter().sorted_by_key(sort_by).collect_vec()
}
}
pub struct ClientContext {
pub server: Arc<ServerContext>,
pub conn: RwLock<MinecraftConnection<TcpStream>>,
pub addr: SocketAddr,
protocol_version: AtomicI32,
server_address: RwLock<String>,
server_port: AtomicU16,
}
impl ClientContext {
pub fn new(
server: Arc<ServerContext>,
conn: MinecraftConnection<TcpStream>
) -> ClientContext {
ClientContext {
server,
addr: conn.get_ref().peer_addr().unwrap(),
conn: RwLock::new(conn),
protocol_version: AtomicI32::default(),
server_address: RwLock::new(String::new()),
server_port: AtomicU16::default()
}
}
pub fn handshake(
self: &Arc<Self>,
protocol_version: i32,
server_address: String,
server_port: u16
) -> () {
self.protocol_version.store(protocol_version, Ordering::SeqCst);
self.server_port.store(server_port, Ordering::SeqCst);
*self.server_address.write().unwrap() = server_address;
}
pub fn protocol_version(self: &Arc<Self>) -> i32 {
self.protocol_version.load(Ordering::SeqCst)
}
pub fn server_port(self: &Arc<Self>) -> u16 {
self.server_port.load(Ordering::SeqCst)
}
pub fn server_address(self: &Arc<Self>) -> String {
self.server_address.read().unwrap().clone()
}
pub fn conn(self: &Arc<Self>) -> RwLockWriteGuard<'_, MinecraftConnection<TcpStream>> {
self.conn.write().unwrap()
}
}
pub trait Listener: Sync + Send {
fn on_status_priority(&self) -> i8 { 0 }
fn on_status(&self, _: Arc<ClientContext>, _: &mut String) -> Result<(), ServerError> { Ok(()) }
}
pub trait PacketHandler: Sync + Send {
fn on_incoming_packet_priority(&self) -> i8 { 0 }
fn on_incoming_packet(&self, _: Arc<ClientContext>, _: &mut Packet) -> Result<(), ServerError> { Ok(()) }
fn on_outcoming_packet_priority(&self) -> i8 { 0 }
fn on_outcoming_packet(&self, _: Arc<ClientContext>, _: &mut Packet) -> Result<(), ServerError> { Ok(()) }
}

View File

@ -137,13 +137,13 @@ impl TextComponentBuilder {
}
}
pub fn text(mut self, text: String) -> Self {
self.text = text;
pub fn text(mut self, text: &str) -> Self {
self.text = text.to_string();
self
}
pub fn color(mut self, color: String) -> Self {
self.color = Some(color);
pub fn color(mut self, color: &str) -> Self {
self.color = Some(color.to_string());
self
}

View File

@ -1,6 +1,7 @@
use std::{env::args, io::{Read, Write}, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration};
use config::ServerConfig;
use context::{ClientContext, Listener, PacketHandler, ServerContext};
use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet};
use data::{ServerError, TextComponent};
@ -8,8 +9,72 @@ use pohuy::Pohuy;
pub mod config;
pub mod data;
pub mod context;
pub mod pohuy;
struct ExampleListener;
impl Listener for ExampleListener {
fn on_status(&self, client: Arc<ClientContext>, response: &mut String) -> Result<(), ServerError> {
*response = format!(
"{{
\"version\": {{
\"name\": \"idk\",
\"protocol\": {}
}},
\"players\": {{
\"max\": 100,
\"online\": 42,
\"sample\": [
{{
\"name\": \"Жопа\",
\"id\": \"00000000-0000-0000-0000-000000000000\"
}}
]
}},
\"description\": {},
\"favicon\": \"data:image/png;base64,<data>\",
\"enforcesSecureChat\": false
}}",
client.protocol_version(),
TextComponent::builder()
.text("Hello World! ")
.extra(vec![
TextComponent::builder()
.text("Protocol: ")
.color("gold")
.extra(vec![
TextComponent::builder()
.text(&client.protocol_version().to_string())
.underlined(true)
.build()
])
.build(),
TextComponent::builder()
.text("\nServer Addr: ")
.color("green")
.extra(vec![
TextComponent::builder()
.text(&format!("{}:{}", client.server_address(), client.server_port()))
.underlined(true)
.build()
])
.build()
])
.build()
.to_string()?
);
Ok(())
}
}
struct ExamplePacketHandler;
impl PacketHandler for ExamplePacketHandler {}
fn main() {
// Получение аргументов
let exec = args().next().expect("Неизвестная система");
@ -37,28 +102,45 @@ fn main() {
// но мы этого делать не будем чтобы не было мемори лик лишнего
let config = Arc::new(config);
// Создаем контекст сервера
// Передается во все подключения
let mut server = ServerContext::new(config);
server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера
server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера
// Бетонируем сервер контекст от изменений
let server = Arc::new(server);
// Биндим сервер где надо
let Ok(server) = TcpListener::bind(&config.host) else {
println!("Не удалось забиндить сервер на {}", &config.host);
let Ok(listener) = TcpListener::bind(&server.config.host) else {
println!("Не удалось забиндить сервер на {}", &server.config.host);
return;
};
println!("Сервер запущен на {}", &config.host);
println!("Сервер запущен на {}", &server.config.host);
while let Ok((stream, addr)) = server.accept() {
let config = config.clone();
while let Ok((stream, addr)) = listener.accept() {
let server = server.clone();
thread::spawn(move || {
println!("Подключение: {}", addr);
// Установка таймаутов на чтение и запись
// По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг
stream.set_read_timeout(Some(Duration::from_secs(config.timeout))).pohuy();
stream.set_write_timeout(Some(Duration::from_secs(config.timeout))).pohuy();
stream.set_read_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy();
stream.set_write_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy();
// Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия
let conn = MinecraftConnection::new(stream);
// Создаем контекст клиента
// Передавется во все листенеры и хандлеры чтобы определять именно этот клиент
let client = Arc::new(ClientContext::new(server, conn));
// Обработка подключения
// Если ошибка -> выводим
match handle_connection(config, MinecraftConnection::new(&stream)) {
match handle_connection(client) {
Ok(_) => {},
Err(error) => {
println!("Ошибка подключения: {error:?}");
@ -71,11 +153,13 @@ fn main() {
}
fn handle_connection(
_: Arc<ServerConfig>, // Конфиг сервера (возможно будет использоаться в будущем)
mut conn: MinecraftConnection<impl Read + Write> // Подключение
client: Arc<ClientContext>, // Контекст клиента
) -> Result<(), ServerError> {
// Чтение рукопожатия
let mut packet = conn.read_packet()?;
// Получение пакетов производится через client.conn(),
// ВАЖНО: не помещать сам client.conn() в переменные,
// он должен сразу убиваться иначе соединение гдето задедлочится
let mut packet = client.conn().read_packet()?;
if packet.id() != 0x00 {
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия")));
@ -86,56 +170,41 @@ fn handle_connection(
let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же
let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения
client.handshake(protocol_version, server_address, server_port);
match next_state {
1 => { // Тип подключения - статус
loop {
// Чтение запроса
let packet = conn.read_packet()?;
let packet = client.conn().read_packet()?;
match packet.id() {
0x00 => { // Запрос статуса
let mut packet = Packet::empty(0x00);
// Дефолтный статус
let mut status = "{
\"version\": {
\"name\": \"Error\",
\"protocol\": 0
},
\"description\": {\"text\": \"Internal server error\"}
}".to_string();
// Опрос всех листенеров
for listener in client.server.listeners( // Цикл по листенерам
|o| o.on_status_priority() // Сортировка по приоритетности
).iter() {
listener.on_status(client.clone(), &mut status)?; // Вызов метода листенера
}
// Отправка статуса
// В будущем это надо будет переделать чтобы это отправлялось через Listener'ы а не самим ядром сервера
// Хотя можно сделать и дефолтное значение через конфиг
packet.write_string(&format!(
// Пример статуса
"{{
\"version\": {{
\"name\": \"1.21.5\",
\"protocol\": {protocol_version}
}},
\"players\": {{
\"max\": 100,
\"online\": 5,
\"sample\": [
{{
\"name\": \"thinkofdeath\",
\"id\": \"4566e69f-c907-48ee-8d71-d7ba5aa00d20\"
}}
]
}},
\"description\": {},
\"favicon\": \"data:image/png;base64,<data>\",
\"enforcesSecureChat\": false
}}",
packet.write_string(&status)?;
// В MOTD пихаем дебаг инфу
TextComponent::builder()
.text(format!("pv: {protocol_version}, sp: {server_port}\nsa: {server_address}"))
.color("red".to_string())
.bold(true)
.italic(true)
.underlined(true)
.build()
.to_string()?
))?;
conn.write_packet(&packet)?;
client.conn().write_packet(&packet)?;
},
0x01 => { // Пинг
conn.write_packet(&packet)?;
client.conn().write_packet(&packet)?;
// Просто отправляем этот же пакет обратно
// ID такой-же, содержание тоже, так почему бы и нет?
},
@ -151,13 +220,13 @@ fn handle_connection(
let mut packet = Packet::empty(0x00);
packet.write_string(&TextComponent::builder()
.text(format!("This server is in developement!!"))
.color("gold".to_string())
.text("This server is in developement!!")
.color("gold")
.bold(true)
.build()
.to_string()?)?;
conn.write_packet(&packet)?;
client.conn().write_packet(&packet)?;
// TODO: Чтение Configuration (возможно с примешиванием Listener'ов)
// TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы