Last active
October 23, 2023 12:55
-
-
Save boranby/787343c64d6c5292369fd722977641ea to your computer and use it in GitHub Desktop.
client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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