1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-16 06:10:26 +02:00

Beginnings of an async chat exercise (#627)

* beginnings of an async chat exercise

* really basic solution

* format
This commit is contained in:
Dustin J. Mitchell
2023-05-16 11:51:01 -04:00
committed by GitHub
parent b4fb870af6
commit caeabdae3e
5 changed files with 168 additions and 5 deletions

View File

@ -0,0 +1,11 @@
[package]
name = "chat-async"
version = "0.1.0"
edition = "2021"
[dependencies]
futures-util = "0.3.28"
http = "0.2.9"
thiserror = "1.0.40"
tokio = { version = "1.28.1", features = ["net", "macros", "time", "rt", "rt-multi-thread", "io-std", "io-util"] }
tokio-websockets = "0.3.0"

View File

@ -0,0 +1,35 @@
use futures_util::SinkExt;
use http::Uri;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio_websockets::{ClientBuilder, Message};
#[tokio::main]
async fn main() -> Result<(), tokio_websockets::Error> {
let mut ws_stream = ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000"))
.connect()
.await?;
let stdin = tokio::io::stdin();
let mut stdin = BufReader::new(stdin);
loop {
let mut line = String::new();
tokio::select! {
incoming = ws_stream.next() => {
match incoming {
Some(Ok(msg)) => println!("From server: {}", msg.as_text()?),
Some(Err(err)) => return Err(err.into()),
None => return Ok(()),
}
}
res = stdin.read_line(&mut line) => {
match res {
Ok(0) => return Ok(()),
Ok(_) => ws_stream.send(Message::text(line.trim_end().to_string())).await?,
Err(err) => return Err(err.into()),
}
}
}
}
}

View File

@ -0,0 +1,82 @@
use futures_util::sink::SinkExt;
use std::net::SocketAddr;
use thiserror::Error;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::broadcast::error::{RecvError, SendError};
use tokio::sync::broadcast::{channel, Sender};
use tokio_websockets::{Message, ServerBuilder, WebsocketStream};
#[derive(Error, Debug)]
enum ServerError {
#[error("websocket error: {0}")]
Websocket(String),
#[error("io error: {0}")]
IO(#[from] std::io::Error),
#[error("broadcast channel SendError: {0}")]
SendError(#[from] SendError<String>),
#[error("broadcast channel RecvError: {0}")]
RecvError(#[from] RecvError),
}
// tokio_websockets Error types do not implement std::error::Error, so we make do by just capturing
// the debug format for the error.
impl From<tokio_websockets::Error> for ServerError {
fn from(err: tokio_websockets::Error) -> Self {
ServerError::Websocket(format!("{:?}", err))
}
}
impl From<tokio_websockets::proto::ProtocolError> for ServerError {
fn from(err: tokio_websockets::proto::ProtocolError) -> Self {
ServerError::Websocket(format!("{:?}", err))
}
}
async fn handle_connection(
addr: SocketAddr,
mut ws_stream: WebsocketStream<TcpStream>,
bcast_tx: Sender<String>,
) -> Result<(), ServerError> {
ws_stream
.send(Message::text("Welcome to chat! Type a message".into()))
.await?;
let mut bcast_rx = bcast_tx.subscribe();
loop {
tokio::select! {
incoming = ws_stream.next() => {
match incoming {
Some(Ok(msg)) => {
let msg = msg.as_text()?;
println!("From client {addr:?} {msg:?}");
bcast_tx.send(msg.into())?;
}
Some(Err(err)) => return Err(err.into()),
None => return Ok(()),
}
}
msg = bcast_rx.recv() => {
ws_stream.send(Message::text(msg?)).await?;
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), ServerError> {
let (bcast_tx, _) = channel(16);
let listener = TcpListener::bind("127.0.0.1:2000").await?;
println!("listening on port 2000");
loop {
let (socket, addr) = listener.accept().await?;
println!("New connection from {addr:?}");
let bcast_tx = bcast_tx.clone();
tokio::spawn(async move {
// Wrap the raw TCP stream into a websocket.
let ws_stream = ServerBuilder::new().accept(socket).await?;
handle_connection(addr, ws_stream, bcast_tx).await
});
}
}