protohackers/problem_03/src/server.rs

141 lines
5 KiB
Rust
Raw Normal View History

use futures::{stream::StreamExt, SinkExt};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use tokio::net::TcpListener;
use tokio::sync::{
broadcast,
broadcast::{Receiver, Sender},
};
use tokio_util::codec::{Framed, LinesCodec};
use tracing::{error, info};
2023-04-27 13:23:56 +00:00
const IP: &str = "0.0.0.0";
const PORT: u16 = 1222;
2023-04-27 13:23:56 +00:00
type Error = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, Error>;
2023-04-27 13:23:56 +00:00
type Username = String;
2023-04-29 08:01:48 +00:00
type Message = String;
2023-04-29 12:44:01 +00:00
type Address = SocketAddr;
2023-04-29 08:01:48 +00:00
#[derive(Clone, Debug, Default)]
2023-04-29 12:44:01 +00:00
struct BroadcastMessage(Username, Message);
2023-04-29 08:01:48 +00:00
#[derive(Clone, Debug, Default)]
2023-04-29 12:44:01 +00:00
struct Users(Arc<Mutex<HashMap<Username, Address>>>);
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::try_init().expect("Tracing was not setup");
2023-04-27 13:23:56 +00:00
let listener = TcpListener::bind(format!("{IP}:{PORT}")).await?;
info!("Listening on: {}", format!("{IP}:{PORT}"));
let (tx, _) = broadcast::channel(256);
let db = Users::default();
// Infinite loop to always listen to new connections on this IP/PORT
2023-04-27 13:23:56 +00:00
loop {
let (stream, address) = listener.accept().await?;
2023-04-29 16:34:47 +00:00
let (tx, rx) = (tx.clone(), tx.subscribe());
let db = db.clone();
2023-04-27 13:23:56 +00:00
tokio::spawn(async move {
let mut framed = Framed::new(stream, LinesCodec::new());
info!("New address connected: {address}");
let _ = framed
.send("Welcome to budgetchat! What shall I call you?".to_string())
.await;
let mut name = String::default();
// We read exactly one line per loop. A line ends with \n.
// So if the client doesn't frame their package with \n at the end,
// we won't process until we find one.
match framed.next().await {
Some(Ok(username)) => {
2023-04-29 17:01:11 +00:00
if !username.is_empty() && name.chars().all(char::is_alphanumeric) {
2023-04-29 12:03:10 +00:00
name = username.clone();
2023-04-29 12:44:01 +00:00
db.0.lock().unwrap().insert(username.clone(), address);
let message = compose_message(username.clone(), db.clone());
info!("Adding username: {username} to db");
2023-04-29 12:03:10 +00:00
let _ = framed.send(message).await;
} else {
return;
}
}
Some(Err(e)) => {
error!("Error parsing message: {e}");
2023-04-29 16:08:32 +00:00
return;
}
None => {
info!("No frame");
2023-04-29 16:08:32 +00:00
return;
}
}
2023-04-29 16:34:47 +00:00
let b = BroadcastMessage(
name.clone(),
format!("* {} has entered the room", name),
);
let _ = tx.send(b);
let mut rx = rx.resubscribe();
loop {
tokio::select! {
n = framed.next() => {
match n {
Some(Ok(n)) => {
// broadcast message to all clients except the one who sent it
info!("Receiving new chat message: {n}");
2023-04-29 08:01:48 +00:00
let b =
2023-04-29 16:08:32 +00:00
BroadcastMessage(name.clone(), format!("[{}] {}", name, n));
2023-04-29 08:01:48 +00:00
let _ = tx.send(b);
}
Some(Err(e)) => {
error!("Error receiving chat message: {e}");
}
None => {
// Connection dropped
// remove client from db etc.
// send leave message
info!("No next frame");
2023-04-29 08:01:48 +00:00
let b =
2023-04-29 12:44:01 +00:00
BroadcastMessage(name.clone(), format!("* {} has left the room", name));
db.0.lock().unwrap().remove(&name.clone());
2023-04-29 08:01:48 +00:00
let _ = tx.send(b);
break;
}
}
2023-04-27 13:23:56 +00:00
}
message = rx.recv() => {
2023-04-29 08:01:48 +00:00
let broadcast = message.clone().unwrap();
info!("Broadcast received: {:?}", message.clone().unwrap());
2023-04-29 12:44:01 +00:00
if broadcast.0 != name {
2023-04-29 08:10:59 +00:00
info!("Broadcast sent to {}: {:?}", name, message.clone().unwrap());
2023-04-29 08:01:48 +00:00
let _ = framed.send(message.unwrap().1).await;
}
}
}
2023-04-27 13:23:56 +00:00
}
});
}
}
2023-04-29 12:44:01 +00:00
fn compose_message(name: String, db: Users) -> String {
format!(
"* The room contains: {}",
db.0.lock()
.unwrap()
2023-04-29 12:44:01 +00:00
.keys()
.filter(|n| n.as_str() != name)
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}