You've already forked comprehensive-rust
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:
committed by
GitHub
parent
b4fb870af6
commit
caeabdae3e
11
src/exercises/concurrency/chat-async/Cargo.toml
Normal file
11
src/exercises/concurrency/chat-async/Cargo.toml
Normal 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"
|
35
src/exercises/concurrency/chat-async/src/bin/client.rs
Normal file
35
src/exercises/concurrency/chat-async/src/bin/client.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
82
src/exercises/concurrency/chat-async/src/bin/server.rs
Normal file
82
src/exercises/concurrency/chat-async/src/bin/server.rs
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user