use std::net::{TcpListener, TcpStream, UdpSocket};
use std::io::{Read, Write, self};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::thread;
use std::fs::OpenOptions;
use std::time::SystemTime;
use std::fmt::Write as FmtWrite;
type ClientMap = Arc<Mutex<HashMap<String, TcpStream>>>;
type LogFile = Arc<Mutex<io::BufWriter<std::fs::File>>>;
fn log_message(log_file: &LogFile, msg: &str) {
let mut file = log_file.lock().unwrap();
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut log_entry = String::new();
write!(&mut log_entry, "[{}] {}", timestamp, msg).unwrap();
file.write_all(log_entry.as_bytes()).unwrap();
file.flush().unwrap();
print!("{}", msg);
}
fn get_local_ip() -> String {
UdpSocket::bind("0.0.0.0:0")
.and_then(|s| {
s.connect("8.8.8.8:80")?;
Ok(s.local_addr()?.ip().to_string())
})
.unwrap_or_else(|_| "0.0.0.0".into())
}
fn handle_client(mut stream: TcpStream, clients: ClientMap, log_file: LogFile) {
let client_addr = stream.peer_addr().unwrap().to_string();
let connect_msg = format!("[Connected] {}\n", client_addr);
log_message(&log_file, &connect_msg);
{
let mut clients = clients.lock().unwrap();
clients.insert(client_addr.clone(), stream.try_clone().unwrap());
}
let mut buffer = [0; 1024];
loop {
match stream.read(&mut buffer) {
Ok(0) => break,
Ok(n) => {
let raw_msg = String::from_utf8_lossy(&buffer[..n]);
let server_msg = format!("[Message] {}: {}\n", client_addr, raw_msg.trim());
let broadcast_msg = format!("[{}] {}\n", client_addr, raw_msg.trim());
log_message(&log_file, &server_msg);
let mut clients = clients.lock().unwrap();
clients.retain(|addr, client| {
if addr == &client_addr { return true; }
client.write_all(broadcast_msg.as_bytes()).is_ok()
});
}
Err(e) => {
let err_msg = format!("[Error] {}: {}\n", client_addr, e);
log_message(&log_file, &err_msg);
break;
}
}
}
clients.lock().unwrap().remove(&client_addr);
let disconnect_msg = format!("[Disconnected] {}\n", client_addr);
log_message(&log_file, &disconnect_msg);
}
fn start(path: &str) -> io::Result<()> {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
let log_file = Arc::new(Mutex::new(io::BufWriter::new(file)));
let listener = TcpListener::bind("0.0.0.0:0")?;
let clients: ClientMap = Arc::new(Mutex::new(HashMap::new()));
let local_ip = get_local_ip();
let port = listener.local_addr()?.port();
let startup_msg = format!("[Chat Server] {}:{}\n", local_ip, port);
log_message(&log_file, &startup_msg);
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let clients = Arc::clone(&clients);
let log_file = Arc::clone(&log_file);
thread::spawn(move || handle_client(stream, clients, log_file));
}
Err(e) => {
let err_msg = format!("[Error] Failed to accept connection: {}\n", e);
log_message(&log_file, &err_msg);
}
}
}
Ok(())
}
use std::path::Path;
use std::fs;
pub fn start_chat_server(){
let path_text = "./runtime/chat/";
let dir_path = Path::new(&path_text);
if dir_path.exists() && dir_path.is_dir() {
} else {
if let Err(e) = fs::create_dir_all(dir_path) {
eprintln!("Failed to create directory: {}", e);
} else {
}
}
let _ = start(&(path_text.to_owned() + "rust_chat_server.log"));
}