listeners and handlers
This commit is contained in:
parent
103b8314f7
commit
f8684a0402
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
118
src/context.rs
Normal 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(()) }
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
171
src/main.rs
171
src/main.rs
@ -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'ы
|
||||
|
Loading…
Reference in New Issue
Block a user