alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates server src dedicated transport.rs
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::net::{Shutdown, SocketAddr, TcpStream};
use std::sync::mpsc::{self, SyncSender};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use common::protocol::{Bridge, Connection, PROTOCOL_VERSION, Packet};
use crate::disconnect;
use crate::save::valid_name;
/// A player who completed the offline handshake.
pub(super) struct Login {
pub name: String,
pub address: SocketAddr,
pub connection: Connection,
}
/// `NetLoginHandler` gives up after 600 ticks however slowly the bytes arrive.
const LOGIN_DEADLINE: Duration = Duration::from_secs(30);
/// Reads with a total deadline while logging in, then with the ordinary socket timeout.
struct Socket {
stream: TcpStream,
deadline: Option<Instant>,
}
impl Read for Socket {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(deadline) = self.deadline {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"Took too long to log in",
));
}
self.stream.set_read_timeout(Some(remaining))?;
}
self.stream.read(buf)
}
}
pub(super) struct Peer {
stream: TcpStream,
thread: JoinHandle<()>,
}
impl Peer {
pub fn start(stream: TcpStream, incoming: SyncSender<Login>) -> io::Result<Self> {
stream.set_nodelay(true)?;
stream.set_read_timeout(Some(Duration::from_secs(30)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
let socket = stream.try_clone()?;
let thread = std::thread::Builder::new()
.name("client-connection".into())
.spawn(move || {
if let Err(error) = serve(socket, incoming) {
tracing::debug!("connection closed: {error}");
}
})?;
Ok(Self { stream, thread })
}
pub fn finished(&self) -> bool {
self.thread.is_finished()
}
}
impl Drop for Peer {
fn drop(&mut self) {
let _ = self.stream.shutdown(Shutdown::Both);
}
}
fn serve(stream: TcpStream, incoming: SyncSender<Login>) -> io::Result<()> {
let address = stream.peer_addr()?;
let mut output = BufWriter::new(stream.try_clone()?);
let mut input = BufReader::new(Socket {
stream,
deadline: Some(Instant::now() + LOGIN_DEADLINE),
});
let name = match login(&mut input, &mut output) {
Ok(name) => name,
Err(error) => {
let _ = disconnect(error.to_string()).write(&mut output);
let _ = output.flush();
return Err(error);
}
};
let (bridge, server) = common::protocol::bridged_connection();
input.get_mut().deadline = None;
input
.get_ref()
.stream
.set_read_timeout(Some(Duration::from_secs(30)))?;
let sender = bridge.incoming.clone();
let reader = std::thread::Builder::new()
.name("client-packets".into())
.spawn(move || {
loop {
let packet = Packet::read_from_client(&mut input)
.unwrap_or_else(|error| disconnect(error.to_string()));
let disconnected = matches!(packet, Packet::Disconnect { .. });
if sender.send(packet).is_err() || disconnected {
break;
}
}
})?;
let login = Login {
name,
address,
connection: server,
};
// The player joins only once its packets can be read
let result = if incoming.try_send(login).is_err() {
Err(io::Error::other("server login queue is full"))
} else {
forward(&bridge, &reader, &mut output)
};
let _ = output.get_ref().shutdown(Shutdown::Both);
let _ = reader.join();
result
}
/// Writes the server's packets to the client until either side disconnects.
fn forward(bridge: &Bridge, reader: &JoinHandle<()>, output: &mut impl Write) -> io::Result<()> {
while !reader.is_finished() {
let packet = match bridge.outgoing.recv_timeout(Duration::from_millis(50)) {
Ok(packet) => packet,
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(mpsc::RecvTimeoutError::Disconnected) => break,
};
let mut disconnected = matches!(packet, Packet::Disconnect { .. });
packet.write(output).map_err(io::Error::other)?;
bridge.written(&packet);
for packet in bridge.outgoing.try_iter() {
disconnected |= matches!(packet, Packet::Disconnect { .. });
packet.write(output).map_err(io::Error::other)?;
bridge.written(&packet);
if disconnected {
break;
}
}
output.flush()?;
if disconnected {
break;
}
}
Ok(())
}
pub(super) fn login(input: &mut impl Read, output: &mut impl Write) -> io::Result<String> {
let Packet::Handshake { .. } = Packet::read_from_client(input).map_err(io::Error::other)?
else {
return Err(io::Error::other("Protocol error"));
};
Packet::Handshake {
username: "-".into(),
}
.write(output)
.map_err(io::Error::other)?;
output.flush()?;
let Packet::Login {
version_or_entity,
username,
..
} = Packet::read_from_client(input).map_err(io::Error::other)?
else {
return Err(io::Error::other("Protocol error"));
};
if version_or_entity != PROTOCOL_VERSION {
return Err(io::Error::other(if version_or_entity > PROTOCOL_VERSION {
"Outdated server!"
} else {
"Outdated client!"
}));
}
if !valid_name(&username) {
return Err(io::Error::other("Invalid username"));
}
Ok(username)
}