mirror of
https://github.com/timvisee/lazymc.git
synced 2025-08-01 04:32:06 -07:00
Switch to server status polling rather than pinging
This commit is contained in:
@@ -5,11 +5,11 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use minecraft_protocol::data::server_status::ServerStatus;
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse};
|
||||
use rand::Rng;
|
||||
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
@@ -22,42 +22,39 @@ const PROTOCOL_VERSION: i32 = 754;
|
||||
/// Monitor ping inverval in seconds.
|
||||
const MONITOR_PING_INTERVAL: u64 = 2;
|
||||
|
||||
/// Ping timeout in seconds.
|
||||
const PING_TIMEOUT: u64 = 8;
|
||||
|
||||
/// Poll server state.
|
||||
///
|
||||
/// Returns `true` if a ping succeeded.
|
||||
pub async fn poll_server(addr: SocketAddr) -> bool {
|
||||
attempt_connect(addr).await.is_ok()
|
||||
}
|
||||
/// Status request timeout in seconds.
|
||||
const STATUS_TIMEOUT: u64 = 8;
|
||||
|
||||
/// Monitor server.
|
||||
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
||||
loop {
|
||||
trace!("Polling {} ... ", addr);
|
||||
let online = poll_server(addr).await;
|
||||
trace!("Fetching status for {} ... ", addr);
|
||||
let status = poll_server(addr).await;
|
||||
|
||||
state.set_online(online);
|
||||
// Update server state
|
||||
state.set_online(status.is_some());
|
||||
if let Some(status) = status {
|
||||
state.set_status(status);
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(MONITOR_PING_INTERVAL)).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Attemp to connect to the given server.
|
||||
async fn attempt_connect(addr: SocketAddr) -> Result<(), ()> {
|
||||
/// Poll server state.
|
||||
///
|
||||
/// Returns server status if connection succeeded.
|
||||
pub async fn poll_server(addr: SocketAddr) -> Option<ServerStatus> {
|
||||
fetch_status(addr).await.ok()
|
||||
}
|
||||
|
||||
/// Attemp to fetch status from server.
|
||||
async fn fetch_status(addr: SocketAddr) -> Result<ServerStatus, ()> {
|
||||
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
|
||||
|
||||
// Send handshake
|
||||
send_handshake(&mut stream, addr).await?;
|
||||
|
||||
// Send ping request
|
||||
let token = send_ping(&mut stream).await?;
|
||||
|
||||
// Wait for ping with timeout
|
||||
wait_for_ping_timeout(&mut stream, token).await?;
|
||||
|
||||
Ok(())
|
||||
request_status(&mut stream).await?;
|
||||
wait_for_status_timeout(&mut stream).await
|
||||
}
|
||||
|
||||
/// Send handshake.
|
||||
@@ -81,29 +78,17 @@ async fn send_handshake(stream: &mut TcpStream, addr: SocketAddr) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send ping requets.
|
||||
///
|
||||
/// Returns sent ping time token on success.
|
||||
async fn send_ping(stream: &mut TcpStream) -> Result<u64, ()> {
|
||||
// Generate a random ping token
|
||||
let token = rand::thread_rng().gen();
|
||||
|
||||
let ping = PingRequest { time: token };
|
||||
|
||||
let mut packet = Vec::new();
|
||||
ping.encode(&mut packet).map_err(|_| ())?;
|
||||
|
||||
let raw = RawPacket::new(proto::STATUS_PACKET_ID_PING, packet)
|
||||
/// Send status request.
|
||||
async fn request_status(stream: &mut TcpStream) -> Result<(), ()> {
|
||||
let raw = RawPacket::new(proto::STATUS_PACKET_ID_STATUS, vec![])
|
||||
.encode()
|
||||
.map_err(|_| ())?;
|
||||
|
||||
stream.write_all(&raw).await.map_err(|_| ())?;
|
||||
|
||||
Ok(token)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for a ping response.
|
||||
async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
/// Wait for a status response.
|
||||
async fn wait_for_status(stream: &mut TcpStream) -> Result<ServerStatus, ()> {
|
||||
// Get stream reader, set up buffer
|
||||
let (mut reader, mut _writer) = stream.split();
|
||||
let mut buf = BytesMut::new();
|
||||
@@ -116,16 +101,10 @@ async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Catch ping response
|
||||
if packet.id == proto::STATUS_PACKET_ID_PING {
|
||||
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
|
||||
// Ensure ping token is correct
|
||||
if ping.time != token {
|
||||
break;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
// Catch status response
|
||||
if packet.id == proto::STATUS_PACKET_ID_STATUS {
|
||||
let status = StatusResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
return Ok(status.server_status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +112,10 @@ async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Wait for a ping response with timeout.
|
||||
async fn wait_for_ping_timeout(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
let ping = wait_for_ping(stream, token);
|
||||
tokio::time::timeout(Duration::from_secs(PING_TIMEOUT), ping)
|
||||
/// Wait for a status response.
|
||||
async fn wait_for_status_timeout(stream: &mut TcpStream) -> Result<ServerStatus, ()> {
|
||||
let status = wait_for_status(stream);
|
||||
tokio::time::timeout(Duration::from_secs(STATUS_TIMEOUT), status)
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use minecraft_protocol::data::server_status::ServerStatus;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::config::SERVER_CMD;
|
||||
@@ -16,6 +17,11 @@ pub struct ServerState {
|
||||
|
||||
/// Server PID.
|
||||
pid: Mutex<Option<u32>>,
|
||||
|
||||
/// Last known server status.
|
||||
///
|
||||
/// Once set, this will remain set, and isn't cleared when the server goes offline.
|
||||
status: Mutex<Option<ServerStatus>>,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
@@ -54,6 +60,11 @@ impl ServerState {
|
||||
pub fn set_pid(&self, pid: Option<u32>) {
|
||||
*self.pid.lock().unwrap() = pid;
|
||||
}
|
||||
|
||||
/// Update the server status.
|
||||
pub fn set_status(&self, status: ServerStatus) {
|
||||
self.status.lock().unwrap().replace(status);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start Minecraft server.
|
||||
|
Reference in New Issue
Block a user