This repository was archived by the owner on Sep 5, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Merged
Changes from 2 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
a64ccf3
start tutorial
matklad ff16fc3
simple broker
matklad a5aa609
graceful shutdown
matklad 70ebbb3
rework error handling
matklad 7a12724
don' overwrite the peer
matklad 6ec454f
handle disconnection
matklad f7a42dc
update server
matklad ccbb702
start client
matklad c61caef
add client
matklad 1cabb29
move tutorial to readme
matklad 3475df8
fix typo
matklad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
#![feature(async_await)] | ||
|
||
use std::{net::ToSocketAddrs, sync::Arc, collections::HashMap}; | ||
|
||
use futures::channel::mpsc; | ||
use futures::SinkExt; | ||
|
||
use async_std::{ | ||
io::BufReader, | ||
prelude::*, | ||
task, | ||
net::{TcpListener, TcpStream}, | ||
}; | ||
|
||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | ||
type Sender<T> = mpsc::UnboundedSender<T>; | ||
type Receiver<T> = mpsc::UnboundedReceiver<T>; | ||
|
||
|
||
fn main() -> Result<()> { | ||
task::block_on(server("127.0.0.1:8080")) | ||
} | ||
|
||
async fn server(addr: impl ToSocketAddrs) -> Result<()> { | ||
let listener = TcpListener::bind(addr).await?; | ||
|
||
let (broker_sender, broker_receiver) = mpsc::unbounded(); | ||
let broker = task::spawn(broker(broker_receiver)); | ||
let mut incoming = listener.incoming(); | ||
while let Some(stream) = incoming.next().await { | ||
let stream = stream?; | ||
println!("Accepting from: {}", stream.peer_addr()?); | ||
let _handle = task::spawn(client(broker_sender.clone(), stream)); | ||
} | ||
broker.await?; | ||
Ok(()) | ||
} | ||
|
||
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> { | ||
let stream = Arc::new(stream); | ||
let reader = BufReader::new(&*stream); | ||
let mut lines = reader.lines(); | ||
|
||
let name = match lines.next().await { | ||
None => Err("peer disconnected immediately")?, | ||
Some(line) => line?, | ||
}; | ||
broker.send(Event::NewPeer { name: name.clone(), stream: Arc::clone(&stream) }).await.unwrap(); | ||
|
||
while let Some(line) = lines.next().await { | ||
let line = line?; | ||
let (dest, msg) = match line.find(':') { | ||
None => continue, | ||
Some(idx) => (&line[..idx], line[idx + 1 ..].trim()), | ||
}; | ||
let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect(); | ||
let msg: String = msg.trim().to_string(); | ||
|
||
broker.send(Event::Message { | ||
from: name.clone(), | ||
to: dest, | ||
msg, | ||
}).await.unwrap(); | ||
} | ||
Ok(()) | ||
} | ||
|
||
async fn client_writer( | ||
mut messages: Receiver<String>, | ||
stream: Arc<TcpStream>, | ||
) -> Result<()> { | ||
let mut stream = &*stream; | ||
while let Some(msg) = messages.next().await { | ||
stream.write_all(msg.as_bytes()).await?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
#[derive(Debug)] | ||
enum Event { | ||
NewPeer { | ||
name: String, | ||
stream: Arc<TcpStream>, | ||
}, | ||
Message { | ||
from: String, | ||
to: Vec<String>, | ||
msg: String, | ||
}, | ||
} | ||
|
||
async fn broker(mut events: Receiver<Event>) -> Result<()> { | ||
let mut peers: HashMap<String, Sender<String>> = HashMap::new(); | ||
|
||
while let Some(event) = events.next().await { | ||
match event { | ||
Event::Message { from, to, msg } => { | ||
for addr in to { | ||
if let Some(peer) = peers.get_mut(&addr) { | ||
peer.send(format!("from {}: {}\n", from, msg)).await? | ||
} | ||
} | ||
} | ||
Event::NewPeer { name, stream} => { | ||
let (client_sender, client_receiver) = mpsc::unbounded(); | ||
peers.insert(name.clone(), client_sender); | ||
let _handle = task::spawn(client_writer(client_receiver, stream)); | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be best to unwrap the error here so that a panic crashes the program instead of silently continuing, perhaps like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is the case where we should not crash. If a single client fails due to some IO error (which might be on the client's side!) other clients should not be affected.
I am planing to write about graceful shutdown next (so that we can reap errors at least in the end) and then about client's disconnections (which will bring us to futures_unordered and just in time error reaping).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough! But, at the very least, surely we should at least print an error on the screen? Maybe like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah makes sense, especially if we gloss over the fact that
eprintln!
is blocking :-)I've actually got a better idea of how to handle disconnections overnight. Instead of futures unorderned and waiting for the tasks to join, we'll just use channels. I think that's what Go does: IIRC, there's no way to wait until go routine is joined, you can only wait until it sends an "I am about to die" message over a channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the strategy that I usually use.
Btw, considering that
println!
andeprintln!
are blocking, should we provide our own versions of them?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but I'm not sure what's the best way of approaching that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cough is it possible to extend clippy by using it as a library? (
async-clippy
)I see a problem here: is it
async_std::println!("fooo").await
orasync_std::eprintln!("foo")
. In general, I think we should encourage usinglog!
here anyways.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking further about this,
println
and friends in an async block is probably a good clippy lint to suggest.