commit
600d3aba88
4 changed files with 1662 additions and 0 deletions
@ -0,0 +1,20 @@ |
|||||||
|
[package] |
||||||
|
name = "rom-emulator" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
env_logger = "0.11.6" |
||||||
|
futures = "0.3.31" |
||||||
|
log = "0.4.25" |
||||||
|
serde = "1.0.217" |
||||||
|
serde_json = "1.0.135" |
||||||
|
tokio = { version = "1.43.0", features = ["full"] } |
||||||
|
tokio-stream = "0.1.17" |
||||||
|
tokio-tungstenite = "0.26.1" |
||||||
|
uuid = "1.12.0" |
||||||
|
warp = "0.3.7" |
||||||
|
|
||||||
|
[[bin]] |
||||||
|
name = "rom-emulator" |
||||||
|
path = "main.rs" |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
use futures::{SinkExt, StreamExt}; |
||||||
|
use log::{error, info}; |
||||||
|
use std::env; |
||||||
|
use std::net::SocketAddr; |
||||||
|
use tokio::net::{TcpListener, TcpStream}; |
||||||
|
use tokio_tungstenite::{accept_async, tungstenite::protocol::Message}; |
||||||
|
|
||||||
|
// this file is overly commented because I'm learning Rust while I program. I want just want to
|
||||||
|
// thnk aloud.
|
||||||
|
#[tokio::main] |
||||||
|
async fn main() { |
||||||
|
// initialize logger
|
||||||
|
env_logger::init(); |
||||||
|
|
||||||
|
// retrieve the iterator from command line args passed in, extract second item, if nth returns
|
||||||
|
// a value, extra it or else if it returns nothing, use the given ip address
|
||||||
|
let addr = env::args() |
||||||
|
.nth(1) |
||||||
|
.unwrap_or_else(|| "127.0.0.1:8080".to_string()); |
||||||
|
|
||||||
|
// SocketAddr is used to represent an IP address and port combo. .parse attempts to convert the
|
||||||
|
// string to a SocketAddr. If successful, returns a Result<SocketAddr, _>. expect() will
|
||||||
|
// terminate the program and print a message if parse returns an error. If parse is successful,
|
||||||
|
// expect() will unwrap the SockAddr from the Result and assign it to addr.
|
||||||
|
let addr: SocketAddr = addr.parse().expect("Invalid Address"); |
||||||
|
|
||||||
|
// attempt to bind the tcplistner to an address. await indicates that this is async.
|
||||||
|
// TCPListener::bind will not block the current thread but instead yield control until the
|
||||||
|
// binding operation completes. If bind fails, it will return an Err.
|
||||||
|
//
|
||||||
|
// The await keyword here tells the runtime that the function will wait for TCPListener::bind
|
||||||
|
// to complete and will not block the thread. Instead, the runtime can schedule other tasks to
|
||||||
|
// run on the same thread while original operation waits for it's completion.
|
||||||
|
let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); |
||||||
|
|
||||||
|
// info! is a macro that logs a message at the info level.
|
||||||
|
info!("Listening on: {}", addr); |
||||||
|
|
||||||
|
// This is a pattern matching loop. It will continuausly process connections as long as the
|
||||||
|
// await returns Ok. If an error occurs, the loop will stop.
|
||||||
|
//
|
||||||
|
// The accept method waits asynchronously for an incoming tCP connection.
|
||||||
|
// When a client connects, it returns a Result containing TcpStream and SocketAddr. TcpStream
|
||||||
|
// is a handle for the client connection, allow for read and writes.
|
||||||
|
while let Ok((stream, _)) = listener.accept().await { |
||||||
|
// tokio::spawn will spawn a new async task to handle the connection. This allows for
|
||||||
|
// multiple clients to connect and to be processed concurrently. Each task runs
|
||||||
|
// independently and does not block server loop.
|
||||||
|
tokio::spawn(handle_connection(stream)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fn handle_connection(stream: TcpStream) { |
||||||
|
// perform the handshake on the given TCP stream
|
||||||
|
// The match expression is used to handle the result of the accept_async function
|
||||||
|
// accept_async will return a Result that is either an Ok(ws) which will hand us the websocket
|
||||||
|
// connection or an error message.
|
||||||
|
let ws_stream = match accept_async(stream).await { |
||||||
|
Ok(ws) => ws, |
||||||
|
Err(e) => { |
||||||
|
error!("Error during the websocket handshake: {}", e); |
||||||
|
return; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// split websocket stream into sender and receiver
|
||||||
|
let (mut sender, mut receiver) = ws_stream.split(); |
||||||
|
|
||||||
|
// handle incoming websocket messages and reverse the string that comes in and then send it
|
||||||
|
// back. Why? Because.
|
||||||
|
while let Some(msg) = receiver.next().await { |
||||||
|
match msg { |
||||||
|
Ok(Message::Text(text)) => { |
||||||
|
let reversed = text.chars().rev().collect::<String>(); |
||||||
|
// .into converts the types when possible. The Message::Text variant expects String
|
||||||
|
// but reversed might not match that exact implementation. Libraries like
|
||||||
|
// tokio-tungstenite may require into.
|
||||||
|
if let Err(e) = sender.send(Message::Text(reversed.into())).await { |
||||||
|
error!("Error sending message: {}", e); |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(Message::Close(_)) => break, |
||||||
|
Ok(_) => (), |
||||||
|
Err(e) => { |
||||||
|
error!("Error processing message: {}", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue