commit 72e07d56334f1ce6a7d9e9d1a5cc09905aa56d3c Author: 409 Date: Wed Sep 25 14:50:44 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afbfa6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/client/target +/server/target diff --git a/client/Cargo.lock b/client/Cargo.lock new file mode 100644 index 0000000..b69a06d --- /dev/null +++ b/client/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "client" +version = "0.1.0" diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000..729587b --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100644 index 0000000..7c20ab1 --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,70 @@ +use std::{ + io::{self, ErrorKind, Read, Write}, net::{Shutdown, TcpStream}, process, thread +}; + +const BUFFER_SIZE: usize = 1024; + +fn main() { + let mut socket = TcpStream::connect("127.0.0.1:42069").unwrap(); + + let read_socket = socket.try_clone().unwrap(); + + thread::spawn(move || { + handle(read_socket); + }); + + loop { + io::stdout().flush().unwrap(); + + let mut input = String::new(); + match io::stdin().read_line(&mut input) { + Ok(n) => { + if n > 0 { + socket.write(input.as_bytes()).unwrap(); + } else { + println!("read failed"); + } + } + Err(e) => { + println!("Error: {}", e); + break; + } + } + } +} + +fn handle(mut socket: TcpStream) { + loop { + let mut buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + match socket.read(&mut buf) { + Ok(n) => { + let msg = String::from_utf8(buf[..n].to_vec()) + .unwrap() + .trim() + .to_string(); + + if n <= 0 { + disconnect(socket); + return; + } + + if msg.is_empty() { + continue; + } + + println!("{}", msg); + } + Err(ref err) if err.kind() == ErrorKind::WouldBlock => (), + Err(_) => { + disconnect(socket); + return; + } + } + } +} + +fn disconnect(socket: TcpStream) { + println!("[EVENT] Disconnected"); + let _ = socket.shutdown(Shutdown::Both); + process::exit(0); +} diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..65a2565 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust-networking" +version = "0.1.0" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..5e9f815 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust-networking" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..356945c --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,111 @@ +use std::collections::HashMap; +use std::io::Write; +use std::sync::{Arc, Mutex}; +use std::{ + io::Read, + net::{Shutdown, SocketAddr, TcpListener, TcpStream}, + sync::mpsc::{channel, Sender}, + thread::{self}, +}; + +const BUFFER_SIZE: usize = 1024; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:42069").unwrap(); + let sockets: Arc>> = Arc::new(Mutex::new(HashMap::new())); + let c_sockets = Arc::clone(&sockets); + let cc_sockets = Arc::clone(&sockets); + + let mut next_socket_id = 0; + + let (tx_broadcast, rx_broadcast) = channel::>(); + let (tx_disconnect, rx_disconnect) = channel::(); + + thread::spawn(move || loop { + let msg = &rx_broadcast.recv().unwrap(); + let lock = sockets.lock().unwrap(); + let sockets = lock.iter(); + + for (_id, mut socket) in sockets { + socket.write(msg).unwrap(); + } + }); + + thread::spawn(move || loop { + let disconnected_socket_id = &rx_disconnect.recv().unwrap(); + let mut sockets = cc_sockets.lock().unwrap(); + sockets.retain(|id, _socket| id != disconnected_socket_id); + }); + + loop { + let (socket, addr) = listener.accept().unwrap(); + let socket_id = next_socket_id; + + c_sockets + .lock() + .unwrap() + .insert(socket_id, socket.try_clone().unwrap()); + next_socket_id = next_socket_id + 1; + + let cloned_broadcast_tx = tx_broadcast.clone(); + let cloned_disconnect_tx = tx_disconnect.clone(); + + println!("[EVENT] {} has connected", addr); + thread::spawn(move || { + handle( + socket_id, + socket, + addr, + cloned_broadcast_tx, + cloned_disconnect_tx, + ); + }); + } +} + +fn handle( + id: u16, + mut socket: TcpStream, + addr: SocketAddr, + sender: Sender>, + disconnect_sender: Sender, +) { + loop { + let mut buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + match socket.read(&mut buf) { + Ok(n) => { + let msg = String::from_utf8(buf[..n].to_vec()) + .unwrap() + .trim() + .to_string(); + + if n <= 0 { + disconnect_socket(id, socket, disconnect_sender); + broadcast_message(&sender, format!("[EVENT] {id} has disconnected.")); + return; + } + + if msg.is_empty() { + continue; + } + + broadcast_message(&sender, format!("[MESSAGE] {id}: {msg}")); + } + Err(_) => { + disconnect_socket(id, socket, disconnect_sender); + broadcast_message(&sender, format!("[EVENT] {id} has disconnected.")); + return; + } + } + } +} + +fn disconnect_socket(id: u16, socket: TcpStream, sender: Sender) { + sender.send(id).unwrap(); + println!("[EVENT] {} has disconnected", socket.peer_addr().unwrap()); + let _ = socket.shutdown(Shutdown::Both); +} + +fn broadcast_message(sender: &Sender>, message: String) { + sender.send(message.as_bytes().to_vec()).unwrap(); +}