Disconnect all clients on server shutdown, shutdown on ctrl-c

This commit is contained in:
Garen Tyler 2021-03-19 19:23:10 -06:00
parent 17d953fc0c
commit f73fdae2c7
7 changed files with 91 additions and 21 deletions

29
Cargo.lock generated
View File

@ -46,6 +46,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cc"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -88,6 +94,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",
"ctrlc",
"fern", "fern",
"lazy_static", "lazy_static",
"log", "log",
@ -98,6 +105,16 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "ctrlc"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c15b8ec3b5755a188c141c1f6a98e76de31b936209bf066b647979e2a84764a9"
dependencies = [
"nix",
"winapi",
]
[[package]] [[package]]
name = "fern" name = "fern"
version = "0.6.0" version = "0.6.0"
@ -191,6 +208,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nix"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
]
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.6"

View File

@ -15,6 +15,7 @@ radix64 = "0.3.0"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
async-trait = "0.1.48" async-trait = "0.1.48"
lazy_static = "1.4.0" lazy_static = "1.4.0"
ctrlc = "3.1.8"
# colorful = "0.2.1" # colorful = "0.2.1"
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2) # ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)
# toml = "0.5.6" # toml = "0.5.6"

View File

@ -13,6 +13,7 @@ pub mod world;
use log::warn; use log::warn;
pub use mctypes::*; pub use mctypes::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::mpsc::{self, Receiver};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
@ -60,7 +61,7 @@ lazy_static! {
} }
/// Set up logging, read the config file, etc. /// Set up logging, read the config file, etc.
pub fn init() { pub fn init() -> Receiver<()> {
// Set up fern logging. // Set up fern logging.
fern::Dispatch::new() fern::Dispatch::new()
.format(move |out, message, record| { .format(move |out, message, record| {
@ -81,6 +82,13 @@ pub fn init() {
.chain(fern::log_file("output.log").unwrap()) .chain(fern::log_file("output.log").unwrap())
.apply() .apply()
.unwrap(); .unwrap();
// Set up the ctrl-c handler.
let (ctrlc_tx, ctrlc_rx) = mpsc::channel();
ctrlc::set_handler(move || {
ctrlc_tx.send(()).expect("Ctrl-C receiver disconnected");
})
.expect("Error setting Ctrl-C handler");
ctrlc_rx
} }
/// Start the server. /// Start the server.

View File

@ -1,16 +1,25 @@
use log::info; use log::info;
use std::sync::mpsc::TryRecvError;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[tokio::main] #[tokio::main]
pub async fn main() { pub async fn main() {
let start_time = Instant::now(); let start_time = Instant::now();
composition::init(); let ctrlc_rx = composition::init();
info!("Starting server..."); info!("Starting server...");
let mut server = composition::start_server().await; let mut server = composition::start_server().await;
info!("Done! Start took {:?}", start_time.elapsed()); info!("Done! Start took {:?}", start_time.elapsed());
// The main server loop. // The main server loop.
loop { loop {
match ctrlc_rx.try_recv() {
Ok(_) => {
server.shutdown().await;
break; // Exit the loop.
}
Err(TryRecvError::Empty) => {} // Doesn't matter if there's nothing for us
Err(TryRecvError::Disconnected) => panic!("Ctrl-C sender disconnected"),
}
server.update().await.unwrap(); server.update().await.unwrap();
std::thread::sleep(Duration::from_millis(2)); std::thread::sleep(Duration::from_millis(2));
} }

View File

@ -108,17 +108,15 @@ pub mod other {
impl TryFrom<Vec<u8>> for MCBoolean { impl TryFrom<Vec<u8>> for MCBoolean {
type Error = &'static str; type Error = &'static str;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
if bytes.len() < 1 { if bytes.is_empty() {
Err("Not enough bytes") Err("Not enough bytes")
} else { } else if bytes[0] == 1u8 {
if bytes[0] == 1u8 {
Ok(MCBoolean::True) Ok(MCBoolean::True)
} else { } else {
Ok(MCBoolean::False) Ok(MCBoolean::False)
} }
} }
} }
}
impl Into<Vec<u8>> for MCBoolean { impl Into<Vec<u8>> for MCBoolean {
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
match self { match self {
@ -148,7 +146,7 @@ pub mod other {
} }
impl From<String> for MCString { impl From<String> for MCString {
fn from(s: String) -> MCString { fn from(s: String) -> MCString {
MCString { value: s.clone() } MCString { value: s }
} }
} }
impl Into<String> for MCString { impl Into<String> for MCString {
@ -227,9 +225,7 @@ pub mod other {
} }
impl From<String> for MCChat { impl From<String> for MCChat {
fn from(s: String) -> MCChat { fn from(s: String) -> MCChat {
MCChat { MCChat { text: s.into() }
text: s.clone().into(),
}
} }
} }
impl Into<String> for MCChat { impl Into<String> for MCChat {
@ -345,6 +341,15 @@ pub mod other {
Err(io_error("Cannot read MCPosition from stream")) Err(io_error("Cannot read MCPosition from stream"))
} }
} }
impl Default for MCPosition {
fn default() -> Self {
MCPosition {
x: 0.into(),
y: 0.into(),
z: 0.into(),
}
}
}
} }
/// All the numbers, from `i8` and `u8` to `i64` and `u64`, plus `VarInt`s. /// All the numbers, from `i8` and `u8` to `i64` and `u64`, plus `VarInt`s.
@ -394,7 +399,7 @@ pub mod numbers {
impl TryFrom<Vec<u8>> for MCByte { impl TryFrom<Vec<u8>> for MCByte {
type Error = &'static str; type Error = &'static str;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
if bytes.len() < 1 { if bytes.is_empty() {
Err("Not enough bytes") Err("Not enough bytes")
} else { } else {
let mut a = [0u8; 1]; let mut a = [0u8; 1];
@ -452,7 +457,7 @@ pub mod numbers {
impl TryFrom<Vec<u8>> for MCUnsignedByte { impl TryFrom<Vec<u8>> for MCUnsignedByte {
type Error = &'static str; type Error = &'static str;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
if bytes.len() < 1 { if bytes.is_empty() {
Err("Not enough bytes") Err("Not enough bytes")
} else { } else {
let mut a = [0u8; 1]; let mut a = [0u8; 1];
@ -1065,7 +1070,7 @@ pub mod numbers {
} }
out.push(temp); out.push(temp);
} }
return out; out
} }
} }
} }

View File

@ -42,6 +42,17 @@ impl Server {
} }
} }
/// Shut down the server.
///
/// Disconnects all clients.
pub async fn shutdown(&mut self) {
info!("Server shutting down.");
for client in self.network_clients.iter_mut() {
let _ = client.disconnect(Some("The server is shutting down")).await;
// We don't care if it doesn't succeed in sending the packet.
}
}
/// Update the network server. /// Update the network server.
/// ///
/// Update each client in `self.network_clients`. /// Update each client in `self.network_clients`.
@ -67,7 +78,9 @@ impl Server {
} }
}); });
for client in self.network_clients.iter_mut() { for client in self.network_clients.iter_mut() {
client.update(num_players).await?; if client.update(num_players).await.is_err() {
client.force_disconnect();
}
} }
// Remove disconnected clients. // Remove disconnected clients.
self.network_clients self.network_clients
@ -87,7 +100,7 @@ impl Server {
/// The network client can only be in a few states, /// The network client can only be in a few states,
/// this enum keeps track of that. /// this enum keeps track of that.
#[derive(PartialEq)] #[derive(PartialEq, Debug)]
pub enum NetworkClientState { pub enum NetworkClientState {
Handshake, Handshake,
Status, Status,
@ -98,6 +111,7 @@ pub enum NetworkClientState {
/// A wrapper to contain everything related /// A wrapper to contain everything related
/// to networking for the client. /// to networking for the client.
#[derive(Debug)]
pub struct NetworkClient { pub struct NetworkClient {
pub id: u128, pub id: u128,
pub connected: bool, pub connected: bool,
@ -126,6 +140,7 @@ impl NetworkClient {
/// Updating could mean connecting new clients, reading packets, /// Updating could mean connecting new clients, reading packets,
/// writing packets, or disconnecting clients. /// writing packets, or disconnecting clients.
pub async fn update(&mut self, num_players: usize) -> tokio::io::Result<()> { pub async fn update(&mut self, num_players: usize) -> tokio::io::Result<()> {
// println!("{:?}", self);
match self.state { match self.state {
NetworkClientState::Handshake => { NetworkClientState::Handshake => {
let handshake = self.get_packet::<Handshake>().await?; let handshake = self.get_packet::<Handshake>().await?;
@ -284,14 +299,12 @@ impl NetworkClient {
let mut disconnect = Disconnect::new(); let mut disconnect = Disconnect::new();
disconnect.reason.text = reason.unwrap_or("Disconnected").into(); disconnect.reason.text = reason.unwrap_or("Disconnected").into();
self.send_packet(disconnect).await?; self.send_packet(disconnect).await?;
// Give the client 10 seconds to disconnect before forcing it.
tokio::time::sleep(Duration::from_secs(10)).await;
self.force_disconnect(); self.force_disconnect();
Ok(()) Ok(())
} }
/// Force disconnect the client by marking it for cleanup as disconnected. /// Force disconnect the client by marking it for cleanup as disconnected.
async fn force_disconnect(&mut self) { fn force_disconnect(&mut self) {
self.connected = false; self.connected = false;
self.state = NetworkClientState::Disconnected; self.state = NetworkClientState::Disconnected;
} }

View File

@ -37,9 +37,14 @@ macro_rules! register_packets {
} }
} }
} }
impl Default for Packet {
fn default() -> Self {
Packet::Null
}
}
$( $(
impl $name { impl $name {
pub fn into_packet(&self) -> Packet { pub fn as_packet(&self) -> Packet {
Packet::$name(self.clone()) Packet::$name(self.clone())
} }
} }