Skip to content

Instantly share code, notes, and snippets.

@boranby
Last active October 23, 2023 12:55
Show Gist options
  • Save boranby/787343c64d6c5292369fd722977641ea to your computer and use it in GitHub Desktop.
Save boranby/787343c64d6c5292369fd722977641ea to your computer and use it in GitHub Desktop.
client
use std::io::{Read, Write};
use std::net::{Ipv4Addr, TcpStream, UdpSocket};
use std::{error, thread};
use std::thread::sleep;
use std::time::{Duration, Instant};
pub struct Client {
udp_socket: UdpSocket,
tcp_stream: TcpStream,
packet_length_buf: [u8; 2],
packet_buf: [u8; 256],
}
impl Client {
#[inline]
pub fn new(tcp_stream_addr: &str) -> Result<Self, Box<dyn error::Error>> {
let udp_socket = UdpSocket::bind(["0.0.0.0", "5001"].join(":"))?;
udp_socket.set_nonblocking(true)?;
udp_socket.join_multicast_v4(&Ipv4Addr::new(225, 0, 0, 1), &Ipv4Addr::UNSPECIFIED)?;
let tcp_stream = TcpStream::connect(tcp_stream_addr)?;
tcp_stream.set_nodelay(true)?;
tcp_stream.set_nonblocking(true)?;
let heartbeat_stream = tcp_stream.try_clone()?;
thread::spawn(move || {
Self::handle_heartbeat(heartbeat_stream)
});
Ok(Self {
udp_socket,
tcp_stream,
packet_length_buf: Default::default(),
packet_buf: [0; 256],
})
}
pub fn login(&mut self) -> Result<(), Box<dyn error::Error>> {
let login_packet = [0, 1, b'L'];
self.write(&login_packet)?;
let packet = self.read()?;
let packet_type = packet
.first().ok_or("packet is empty")?;
match *packet_type {
b'A' => {
println!("login accepted");
return Ok(());
}
_ => {
panic!("Unexpected packet type during login")
}
}
}
#[inline]
pub fn write(&mut self, bytes: &[u8]) -> Result<(), Box<dyn error::Error>> {
self.tcp_stream.write_all(bytes)?;
println!("sent");
Ok(())
}
#[inline]
pub fn read(&mut self) -> Result<&[u8], Box<dyn error::Error>> {
let packet_length = self.read_len()?;
Self::read_exact(&mut self.tcp_stream, &mut self.packet_buf[..packet_length])
}
#[inline]
fn read_len(&mut self) -> Result<usize, Box<dyn error::Error>> {
Self::read_exact(&mut self.tcp_stream, &mut self.packet_length_buf)?;
let packet_length = u16::from_be_bytes(self.packet_length_buf) as usize;
Ok(packet_length)
}
#[inline]
fn read_exact<'buf>(stream: &mut TcpStream, buf: &'buf mut [u8]) -> Result<&'buf [u8], Box<dyn error::Error>> {
loop {
match stream.read_exact(buf) {
Ok(_) => {
return Ok(buf);
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
_ => {
return Err(Box::try_from("read_exact failed").unwrap());
}
}
}
}
fn handle_heartbeat(mut heartbeat_stream: TcpStream) {
let heartbeat_bytes = [0, 1, b'R'];
let mut heartbeat_buffer = [0; 3];
sleep(Duration::from_secs(1));
let timeout = Duration::from_secs(1);
if let Err(e) = heartbeat_stream.write_all(&heartbeat_bytes) {
println!("heartbeat write failed: {}", e);
}
loop {
if let Ok(_) = Self::read_exact(&mut heartbeat_stream, &mut heartbeat_buffer) {
println!("heartbeat received");
}
sleep(timeout);
if let Err(e) = heartbeat_stream.write_all(&heartbeat_bytes) {
println!("heartbeat write failed: {}", e);
} else {
println!("heartbeat sent");
}
}
}
}
fn main() -> Result<(), Box<dyn error::Error>> {
let mut client = Client::new("127.0.0.1:1337")?;
client.login()?;
sleep(Duration::from_secs(10));
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment