diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 84112c08d..0a9400981 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -33,17 +33,21 @@ name = "chat" path = "chat.rs" [[example]] -name = "connect" -path = "connect.rs" +name = "connect-tcp" +path = "connect-tcp.rs" + +[[example]] +name = "connect-udp" +path = "connect-udp.rs" + +[[example]] +name = "echo-tcp" +path = "echo-tcp.rs" [[example]] name = "echo-udp" path = "echo-udp.rs" -[[example]] -name = "echo" -path = "echo.rs" - [[example]] name = "hello_world" path = "hello_world.rs" diff --git a/examples/README.md b/examples/README.md index caab606bb..4c3d10250 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,7 +10,7 @@ cargo run --example $name ``` A good starting point for the examples would be [`hello_world`](hello_world.rs) -and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains +and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains additional guides for some of the examples. For a larger "real world" example, see the [`mini-redis`][redis] repository. diff --git a/examples/connect-tcp.rs b/examples/connect-tcp.rs new file mode 100644 index 000000000..ceb4ef775 --- /dev/null +++ b/examples/connect-tcp.rs @@ -0,0 +1,71 @@ +//! An example of hooking up stdin/stdout to a TCP stream. +//! +//! This example will connect to a socket address specified in the argument list +//! and then forward all data read on stdin to the server, printing out all data +//! received on stdout. Each line entered on stdin will be translated to a TCP +//! packet which is then sent to the remote address. +//! +//! Note that this is not currently optimized for performance, especially +//! around buffer management. Rather it's intended to show an example of +//! working with a client. +//! +//! This example can be quite useful when interacting with the other examples in +//! this repository! Many of them recommend running this as a simple "hook up +//! stdin/stdout to a server" to get up and running. + +#![warn(rust_2018_idioms)] + +use tokio::io::{stdin, stdout}; +use tokio::net::TcpStream; +use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite}; + +use bytes::Bytes; +use futures::{future, Sink, SinkExt, Stream, StreamExt}; +use std::env; +use std::error::Error; +use std::net::SocketAddr; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Parse what address we're going to connect to + let args = env::args().skip(1).collect::>(); + let addr = args + .first() + .ok_or("this program requires at least one argument")?; + let addr = addr.parse::()?; + + let stdin = FramedRead::new(stdin(), BytesCodec::new()); + let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze())); + let stdout = FramedWrite::new(stdout(), BytesCodec::new()); + + connect(&addr, stdin, stdout).await?; + + Ok(()) +} + +pub async fn connect( + addr: &SocketAddr, + mut stdin: impl Stream> + Unpin, + mut stdout: impl Sink + Unpin, +) -> Result<(), Box> { + let mut stream = TcpStream::connect(addr).await?; + let (r, w) = stream.split(); + let mut sink = FramedWrite::new(w, BytesCodec::new()); + // filter map Result stream into just a Bytes stream to match stdout Sink + // on the event of an Error, log the error and end the stream + let mut stream = FramedRead::new(r, BytesCodec::new()) + .filter_map(|i| match i { + //BytesMut into Bytes + Ok(i) => future::ready(Some(i.freeze())), + Err(e) => { + println!("failed to read from socket; error={e}"); + future::ready(None) + } + }) + .map(Ok); + + match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await { + (Err(e), _) | (_, Err(e)) => Err(e.into()), + _ => Ok(()), + } +} diff --git a/examples/connect-udp.rs b/examples/connect-udp.rs new file mode 100644 index 000000000..aee738a16 --- /dev/null +++ b/examples/connect-udp.rs @@ -0,0 +1,91 @@ +//! An example of hooking up stdin/stdout to a UDP stream. +//! +//! This example will connect to a socket address specified in the argument list +//! and then forward all data read on stdin to the server, printing out all data +//! received on stdout. Each line entered on stdin will be translated to a UDP +//! packet which is then sent to the remote address. +//! +//! Note that this is not currently optimized for performance, especially +//! around buffer management. Rather it's intended to show an example of +//! working with a client. +//! +//! This example can be quite useful when interacting with the other examples in +//! this repository! Many of them recommend running this as a simple "hook up +//! stdin/stdout to a server" to get up and running. + +#![warn(rust_2018_idioms)] + +use tokio::io::{stdin, stdout}; +use tokio::net::UdpSocket; +use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite}; + +use bytes::Bytes; +use futures::{Sink, SinkExt, Stream, StreamExt}; +use std::env; +use std::error::Error; +use std::net::SocketAddr; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Parse what address we're going to connect to + let args = env::args().skip(1).collect::>(); + let addr = args + .first() + .ok_or("this program requires at least one argument")?; + let addr = addr.parse::()?; + + let stdin = FramedRead::new(stdin(), BytesCodec::new()); + let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze())); + let stdout = FramedWrite::new(stdout(), BytesCodec::new()); + + connect(&addr, stdin, stdout).await?; + + Ok(()) +} + +pub async fn connect( + addr: &SocketAddr, + stdin: impl Stream> + Unpin, + stdout: impl Sink + Unpin, +) -> Result<(), Box> { + // We'll bind our UDP socket to a local IP/port, but for now we + // basically let the OS pick both of those. + let bind_addr = if addr.ip().is_ipv4() { + "0.0.0.0:0" + } else { + "[::]:0" + }; + + let socket = UdpSocket::bind(&bind_addr).await?; + socket.connect(addr).await?; + + tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?; + + Ok(()) +} + +async fn send( + mut stdin: impl Stream> + Unpin, + writer: &UdpSocket, +) -> Result<(), std::io::Error> { + while let Some(item) = stdin.next().await { + let buf = item?; + writer.send(&buf[..]).await?; + } + + Ok(()) +} + +async fn recv( + mut stdout: impl Sink + Unpin, + reader: &UdpSocket, +) -> Result<(), std::io::Error> { + loop { + let mut buf = vec![0; 1024]; + let n = reader.recv(&mut buf[..]).await?; + + if n > 0 { + stdout.send(Bytes::from(buf)).await?; + } + } +} diff --git a/examples/connect.rs b/examples/connect.rs deleted file mode 100644 index c869de8ff..000000000 --- a/examples/connect.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! An example of hooking up stdin/stdout to either a TCP or UDP stream. -//! -//! This example will connect to a socket address specified in the argument list -//! and then forward all data read on stdin to the server, printing out all data -//! received on stdout. An optional `--udp` argument can be passed to specify -//! that the connection should be made over UDP instead of TCP, translating each -//! line entered on stdin to a UDP packet to be sent to the remote address. -//! -//! Note that this is not currently optimized for performance, especially -//! around buffer management. Rather it's intended to show an example of -//! working with a client. -//! -//! This example can be quite useful when interacting with the other examples in -//! this repository! Many of them recommend running this as a simple "hook up -//! stdin/stdout to a server" to get up and running. - -#![warn(rust_2018_idioms)] - -use futures::StreamExt; -use tokio::io; -use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite}; - -use std::env; -use std::error::Error; -use std::net::SocketAddr; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Determine if we're going to run in TCP or UDP mode - let mut args = env::args().skip(1).collect::>(); - let tcp = match args.iter().position(|a| a == "--udp") { - Some(i) => { - args.remove(i); - false - } - None => true, - }; - - // Parse what address we're going to connect to - let addr = args - .first() - .ok_or("this program requires at least one argument")?; - let addr = addr.parse::()?; - - let stdin = FramedRead::new(io::stdin(), BytesCodec::new()); - let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze())); - let stdout = FramedWrite::new(io::stdout(), BytesCodec::new()); - - if tcp { - tcp::connect(&addr, stdin, stdout).await?; - } else { - udp::connect(&addr, stdin, stdout).await?; - } - - Ok(()) -} - -mod tcp { - use bytes::Bytes; - use futures::{future, Sink, SinkExt, Stream, StreamExt}; - use std::{error::Error, io, net::SocketAddr}; - use tokio::net::TcpStream; - use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite}; - - pub async fn connect( - addr: &SocketAddr, - mut stdin: impl Stream> + Unpin, - mut stdout: impl Sink + Unpin, - ) -> Result<(), Box> { - let mut stream = TcpStream::connect(addr).await?; - let (r, w) = stream.split(); - let mut sink = FramedWrite::new(w, BytesCodec::new()); - // filter map Result stream into just a Bytes stream to match stdout Sink - // on the event of an Error, log the error and end the stream - let mut stream = FramedRead::new(r, BytesCodec::new()) - .filter_map(|i| match i { - //BytesMut into Bytes - Ok(i) => future::ready(Some(i.freeze())), - Err(e) => { - println!("failed to read from socket; error={e}"); - future::ready(None) - } - }) - .map(Ok); - - match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await { - (Err(e), _) | (_, Err(e)) => Err(e.into()), - _ => Ok(()), - } - } -} - -mod udp { - use bytes::Bytes; - use futures::{Sink, SinkExt, Stream, StreamExt}; - use std::error::Error; - use std::io; - use std::net::SocketAddr; - use tokio::net::UdpSocket; - - pub async fn connect( - addr: &SocketAddr, - stdin: impl Stream> + Unpin, - stdout: impl Sink + Unpin, - ) -> Result<(), Box> { - // We'll bind our UDP socket to a local IP/port, but for now we - // basically let the OS pick both of those. - let bind_addr = if addr.ip().is_ipv4() { - "0.0.0.0:0" - } else { - "[::]:0" - }; - - let socket = UdpSocket::bind(&bind_addr).await?; - socket.connect(addr).await?; - - tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?; - - Ok(()) - } - - async fn send( - mut stdin: impl Stream> + Unpin, - writer: &UdpSocket, - ) -> Result<(), io::Error> { - while let Some(item) = stdin.next().await { - let buf = item?; - writer.send(&buf[..]).await?; - } - - Ok(()) - } - - async fn recv( - mut stdout: impl Sink + Unpin, - reader: &UdpSocket, - ) -> Result<(), io::Error> { - loop { - let mut buf = vec![0; 1024]; - let n = reader.recv(&mut buf[..]).await?; - - if n > 0 { - stdout.send(Bytes::from(buf)).await?; - } - } - } -} diff --git a/examples/echo.rs b/examples/echo-tcp.rs similarity index 93% rename from examples/echo.rs rename to examples/echo-tcp.rs index 045950ade..7ced86acb 100644 --- a/examples/echo.rs +++ b/examples/echo-tcp.rs @@ -9,13 +9,13 @@ //! //! To see this server in action, you can run this in one terminal: //! -//! cargo run --example echo +//! cargo run --example echo-tcp //! //! and in another terminal you can run: //! -//! cargo run --example connect 127.0.0.1:8080 +//! cargo run --example connect-tcp 127.0.0.1:8080 //! -//! Each line you type in to the `connect` terminal should be echo'd back to +//! Each line you type in to the `connect-tcp` terminal should be echo'd back to //! you! If you open up multiple terminals running the `connect` example you //! should be able to see them all make progress simultaneously. diff --git a/examples/echo-udp.rs b/examples/echo-udp.rs index 8f9ed7088..82fb80a48 100644 --- a/examples/echo-udp.rs +++ b/examples/echo-udp.rs @@ -6,9 +6,9 @@ //! //! and in another terminal you can run: //! -//! cargo run --example connect -- --udp 127.0.0.1:8080 +//! cargo run --example connect-udp 127.0.0.1:8080 //! -//! Each line you type in to the `nc` terminal should be echo'd back to you! +//! Each line you type in to the `connect-udp` terminal should be echo'd back to you! #![warn(rust_2018_idioms)] diff --git a/examples/print_each_packet.rs b/examples/print_each_packet.rs index 3e568bd62..b60ebaa2e 100644 --- a/examples/print_each_packet.rs +++ b/examples/print_each_packet.rs @@ -13,9 +13,9 @@ //! //! and in another terminal you can run: //! -//! cargo run --example connect 127.0.0.1:8080 +//! cargo run --example connect-tcp 127.0.0.1:8080 //! -//! Each line you type in to the `connect` terminal should be written to terminal! +//! Each line you type in to the `connect-tcp` terminal should be written to terminal! //! //! Minimal js example: //! diff --git a/examples/proxy.rs b/examples/proxy.rs index cc912ec95..ff87ebc9e 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -11,11 +11,11 @@ //! //! This in another terminal //! -//! cargo run --example echo +//! cargo run --example echo-tcp //! //! And finally this in another terminal //! -//! cargo run --example connect 127.0.0.1:8081 +//! cargo run --example connect-tcp 127.0.0.1:8081 //! //! This final terminal will connect to our proxy, which will in turn connect to //! the echo server, and you'll be able to see data flowing between them. diff --git a/examples/tinydb.rs b/examples/tinydb.rs index fa5e85264..e0c4a700d 100644 --- a/examples/tinydb.rs +++ b/examples/tinydb.rs @@ -12,9 +12,9 @@ //! //! and next in another windows run: //! -//! cargo run --example connect 127.0.0.1:8080 +//! cargo run --example connect-tcp 127.0.0.1:8080 //! -//! In the `connect` window you can type in commands where when you hit enter +//! In the `connect-tcp` window you can type in commands where when you hit enter //! you'll get a response from the server for that command. An example session //! is: //!