You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Sep 5, 2019. It is now read-only.
Copy file name to clipboardExpand all lines: tutorial.adoc
+214Lines changed: 214 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -223,4 +223,218 @@ That way, a client can directly `.write_all` a message to recipients.
223
223
However, this would be wrong: if Alice sends `bob: foo`, and Charley sends `bob: bar`, Bob might actually receive `fobaor`.
224
224
Sending a message over a socket might require several syscalls, so two concurrent ``.write_all``'s might interfere with each other!
225
225
226
+
As a rule of thumb, only a single task should write to each `TcpStream`.
227
+
So let's create a `client_writer` task which receives messages over a channel and writes them to the socket.
228
+
This task would be the point of serialization of messages.
229
+
if Alice and Charley send two messages to Bob at the same time, Bob will see the messages in the same order as they arrive in the channel.
226
230
231
+
[source,rust]
232
+
----
233
+
use futures::channel::mpsc; <1>
234
+
use futures::SinkExt;
235
+
236
+
type Sender<T> = mpsc::UnboundedSender<T>; <2>
237
+
type Receiver<T> = mpsc::UnboundedReceiver<T>;
238
+
239
+
async fn client_writer(
240
+
mut messages: Receiver<String>,
241
+
stream: Arc<TcpStream>, <3>
242
+
) -> Result<()> {
243
+
let mut stream = &*stream;
244
+
while let Some(msg) = messages.next().await {
245
+
stream.write_all(msg.as_bytes()).await?;
246
+
}
247
+
Ok(())
248
+
}
249
+
----
250
+
251
+
<1> We will use channels from the `futures` crate.
252
+
<2> For simplicity, we will use `unbounded` channels, and won't be discussing backpressure in this tutorial.
253
+
<3> As `client` and `client_writer` share the same `TcpStream`, we need to put it into an `Arc`.
254
+
Note that because `client` only reads from and `client_writer` only writes to the stream, so we don't get a race here.
255
+
256
+
257
+
== Connecting Readers and Writers
258
+
259
+
So how we make sure that messages read in `client` flow into the relevant `client_writer`?
260
+
We should somehow maintain an `peers: HashMap<String, Sender<String>>` map which allows a client to find destination channels.
261
+
However, this map would be a bit of shared mutable state, so we'll have to wrap an `RwLock` over it and answer tough questions of what should happen if the client joins at the same moment as it receives a message.
262
+
263
+
One trick to make reasoning about state simpler comes from the actor model.
264
+
We can create a dedicated broker tasks which owns the `peers` map and communicates with other tasks by channels.
265
+
By hiding `peers` inside such "actor" task, we remove the need for mutxes and also make serialization point explicit.
266
+
The order of events "Bob sends message to Alice" and "Alice joins" is determined by the order of the corresponding events in the broker's event queue.
let (client_sender, client_receiver) = mpsc::unbounded();
425
+
peers.insert(name.clone(), client_sender);
426
+
let _handle = task::spawn(client_writer(client_receiver, stream));
427
+
}
428
+
}
429
+
}
430
+
Ok(())
431
+
}
432
+
----
433
+
434
+
<1> Inside the `server`, we create broker's channel and `task`.
435
+
<2> After ``server``'s end, we join broker to make sure all pending messages are delivered.
436
+
Note that this doesn't quite do the trick yet, as we are not joining readers and writers themselves.
437
+
<3> Inside `client`, we need to wrap `TcpStream` into an `Arc`, to be able to share it with the `client_writer`.
438
+
<4> On login, we notify the broker.
439
+
Note that we `.unwrap` on send: broker should outlive all the clients and if that's not the case the broker probably panicked, so we can escalate the panic as well.
440
+
<5> Similarly, we forward parsed messages to the broker, assuming that it is alive.
0 commit comments