Skip to content

Commit 87015e2

Browse files
authored
More beginner-friendly TCP server example (#102)
I wanted to write an example to help guide people new to asynchronous programming, with fewer external dependencies (as some people requested in #66). The example starts with the [single threaded TCP server from the Rust book](https://doc.rust-lang.org/book/ch20-01-single-threaded.html) and uses async functionality to add concurrency. The example goes through: - some examples of blocking code and how to make them non blocking (should address #64 as well) - spawning tasks vs using combinators to run them concurrently on a single thread - testing code by creating mocks and implementing async read/write traits for the mocks
1 parent 1865ce0 commit 87015e2

File tree

27 files changed

+597
-199
lines changed

27 files changed

+597
-199
lines changed

ci/dictionary.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ FutOne
2424
FutTwo
2525
FuturesUnordered
2626
GenFuture
27+
gRPC
28+
html
2729
http
2830
Hyper's
2931
impl
@@ -33,8 +35,13 @@ IoBlocker
3335
IOCP
3436
IoObject
3537
kqueue
38+
localhost
39+
LocalExecutor
3640
metadata
41+
MockTcpStream
42+
multi
3743
multithreaded
44+
multithreading
3845
Mutex
3946
MyError
4047
MyFut
@@ -51,15 +58,18 @@ proxying
5158
pseudocode
5259
ReadIntoBuf
5360
recognise
61+
refactor
5462
RefCell
5563
repurposed
5664
requeue
5765
ResponseFuture
5866
reusability
5967
runtime
68+
runtimes
6069
rustc
6170
rustup
6271
SimpleFuture
72+
smol
6373
SocketRead
6474
SomeType
6575
spawner
@@ -69,6 +79,8 @@ struct
6979
subfuture
7080
subfutures
7181
subpar
82+
TcpListener
83+
TcpStream
7284
threadpool
7385
TimerFuture
7486
TODO

examples/01_05_http_server/Cargo.toml

Lines changed: 0 additions & 18 deletions
This file was deleted.

examples/01_05_http_server/src/lib.rs

Lines changed: 0 additions & 91 deletions
This file was deleted.

examples/02_04_executor/src/lib.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
// ANCHOR: imports
44
use {
55
futures::{
6-
future::{FutureExt, BoxFuture},
7-
task::{ArcWake, waker_ref},
6+
future::{BoxFuture, FutureExt},
7+
task::{waker_ref, ArcWake},
88
},
99
std::{
1010
future::Future,
11+
sync::mpsc::{sync_channel, Receiver, SyncSender},
1112
sync::{Arc, Mutex},
12-
sync::mpsc::{sync_channel, SyncSender, Receiver},
1313
task::{Context, Poll},
1414
time::Duration,
1515
},
@@ -74,7 +74,10 @@ impl ArcWake for Task {
7474
// Implement `wake` by sending this task back onto the task channel
7575
// so that it will be polled again by the executor.
7676
let cloned = arc_self.clone();
77-
arc_self.task_sender.send(cloned).expect("too many tasks queued");
77+
arc_self
78+
.task_sender
79+
.send(cloned)
80+
.expect("too many tasks queued");
7881
}
7982
}
8083
// ANCHOR_END: arcwake_for_task
@@ -128,4 +131,6 @@ fn main() {
128131
// ANCHOR_END: main
129132

130133
#[test]
131-
fn run_main() { main() }
134+
fn run_main() {
135+
main()
136+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Hello!</title>
6+
</head>
7+
<body>
8+
<h1>Oops!</h1>
9+
<p>Sorry, I don't know what you're asking for.</p>
10+
</body>
11+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "sync_tcp_server"
3+
version = "0.1.0"
4+
authors = ["Your Name <[email protected]"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Hello!</title>
6+
</head>
7+
<body>
8+
<h1>Hello!</h1>
9+
<p>Hi from Rust</p>
10+
</body>
11+
</html>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::fs;
2+
use std::io::prelude::*;
3+
use std::net::TcpListener;
4+
use std::net::TcpStream;
5+
6+
fn main() {
7+
// Listen for incoming TCP connections on localhost port 7878
8+
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
9+
10+
// Block forever, handling each request that arrives at this IP address
11+
for stream in listener.incoming() {
12+
let stream = stream.unwrap();
13+
14+
handle_connection(stream);
15+
}
16+
}
17+
18+
fn handle_connection(mut stream: TcpStream) {
19+
// Read the first 1024 bytes of data from the stream
20+
let mut buffer = [0; 1024];
21+
stream.read(&mut buffer).unwrap();
22+
23+
let get = b"GET / HTTP/1.1\r\n";
24+
25+
// Respond with greetings or a 404,
26+
// depending on the data in the request
27+
let (status_line, filename) = if buffer.starts_with(get) {
28+
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
29+
} else {
30+
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
31+
};
32+
let contents = fs::read_to_string(filename).unwrap();
33+
34+
// Write response back to the stream,
35+
// and flush the stream to ensure the response is sent back to the client
36+
let response = format!("{}{}", status_line, contents);
37+
stream.write(response.as_bytes()).unwrap();
38+
stream.flush().unwrap();
39+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "async_tcp_server"
3+
version = "0.1.0"
4+
authors = ["Your Name <[email protected]"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies.async-std]
10+
version = "1.6"
11+
features = ["attributes"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::net::TcpListener;
2+
use std::net::TcpStream;
3+
4+
// ANCHOR: main_func
5+
#[async_std::main]
6+
async fn main() {
7+
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
8+
for stream in listener.incoming() {
9+
let stream = stream.unwrap();
10+
// Warning: This is not concurrent!
11+
handle_connection(stream).await;
12+
}
13+
}
14+
// ANCHOR_END: main_func
15+
16+
// ANCHOR: handle_connection_async
17+
async fn handle_connection(mut stream: TcpStream) {
18+
//<-- snip -->
19+
}
20+
// ANCHOR_END: handle_connection_async
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "slow_request"
3+
version = "0.1.0"
4+
authors = ["Your Name <[email protected]"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies.async-std]
10+
version = "1.6"
11+
features = ["attributes"]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::fs;
2+
use std::io::{Read, Write};
3+
use std::net::TcpListener;
4+
use std::net::TcpStream;
5+
use std::time::Duration;
6+
7+
#[async_std::main]
8+
async fn main() {
9+
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
10+
for stream in listener.incoming() {
11+
let stream = stream.unwrap();
12+
handle_connection(stream).await;
13+
}
14+
}
15+
16+
// ANCHOR: handle_connection
17+
use async_std::task;
18+
19+
async fn handle_connection(mut stream: TcpStream) {
20+
let mut buffer = [0; 1024];
21+
stream.read(&mut buffer).unwrap();
22+
23+
let get = b"GET / HTTP/1.1\r\n";
24+
let sleep = b"GET /sleep HTTP/1.1\r\n";
25+
26+
let (status_line, filename) = if buffer.starts_with(get) {
27+
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
28+
} else if buffer.starts_with(sleep) {
29+
task::sleep(Duration::from_secs(5)).await;
30+
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
31+
} else {
32+
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
33+
};
34+
let contents = fs::read_to_string(filename).unwrap();
35+
36+
let response = format!("{}{}", status_line, contents);
37+
stream.write(response.as_bytes()).unwrap();
38+
stream.flush().unwrap();
39+
}
40+
// ANCHOR_END: handle_connection
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "concurrent_tcp_server"
3+
version = "0.1.0"
4+
authors = ["Your Name <[email protected]"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
futures = "0.3"
11+
12+
[dependencies.async-std]
13+
version = "1.6"
14+
features = ["attributes"]

0 commit comments

Comments
 (0)