Tokio学习

版权声明:本文为BBDXF原创,如果转载请注明出处。 https://blog.csdn.net/bbdxf/article/details/87890145

Tokio 学习

原本想直接学习Actix-Web,一层层看下来,发现内容太多,杂乱,没有目的。所以,一层层剥下来,最终落脚到Tokio上,现在先了解Tokio的相关内容,最终学习Actix相关内容。

Tokio是Rust的一个异步编程库,它是一个事件驱动,使用非阻塞IO编写异步程序的库。总的来说,有几个特点:

  1. 多线程、基于调度的任务窃取(work-stealing)
  2. 一个reactor后端,通过系统事件队列实现(epoll,kqueue,IOCP等)
  3. 异步TCP、UDP Sockets

其他特色:

  • 快速。
    • 零抽象成本。它基于Futures,但是把它编译为了状态机。
    • 并发。通过调度、多线程等利用所有的CPU。
    • 异步IO。Linux上epoll, BSD上kqueue, windows上IOCP。
  • 可靠。将bugs最小化,通过API阻止滥用。
    • 所有权和类型系统。类型安全
    • 反压力。传统基于任务的,当生产者过多,会被存储到内存中,内容过多导致崩溃。Tokio是消费者要使用时才告诉生产者,从而避免这些问题。
    • 可取消。每个计算单元使用一个Future对象,当任务取消,poll就不会调用它。并且取消时,可以实现imp drop来定制清理工作。
  • 轻量级。
    • 没有垃圾回收。
    • 模块化。Tokio虽然开箱即用,但是每个模块都是可拿出来单独使用的。hyper和actix都是基于tokio实现的。

map

The map combinator takes a future and returns a new future that applies a function to the value yielded by the first future.

map接收一个future然后返回一个新的future。新的future会适配一个函数,并且这个函数的参数就是第一个future的返回值。

map主要是将第一个future的结果映射到另外一个,然后返回一个future包装这个结果。

#[macro_use] extern crate futures;
extern crate tokio;

use futures::{Future, Async, Poll};
use std::fmt;

struct HelloWorld;

impl Future for HelloWorld {
    // Item, Error是Future完成后的返回值
    type Item = String;
    type Error = ();

    // 完成状态是 Async::Ready
    // 阻塞状态是 Async::NotReady, 一般不主动返回这个状态
    // 错误状态是 Error
    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
        Ok(Async::Ready("Hello, world!".to_string()))
    }
}

fn main(){
    let f = HelloWorld{}.map(|v| println!("{}", v));
    tokio::run(f);
}

and_then

The and_then combinator allows sequencing two asynchronous operations. Once the first operation completes, the value is passed to a function. The function uses that value to produce a new future and that future is then executed.
The difference between and_then and map is that and_then’s function returns a future where as map’s function returns a value.

and_then 按序执行两个异步操作。第一个future完成后,将值作为参数传递到and_then的参数函数中(作为函数的参数),生成一个新的future,然后执行第二个future。

and_then与map的区别是 and_then返回future, map返回的是一个值。即,and_then后的结果是一个future还需要继续向下调用才能拿到真正的值,而map是返回一个值,不需要继续run。

extern crate tokio;
extern crate bytes;
extern crate futures;

use tokio::io;
use tokio::net::TcpStream;
use futures::Future;

fn main1() {
    let addr = "127.0.0.1:1234".parse().unwrap();

    let f1 = TcpStream::connect(&addr)
        .and_then(|socket| {
            io::write_all(socket, b"hello world")
        })
        .map(|_| println!("write complete"))
        .map_err(|_| println!("failed"));

    tokio::run(f1);
}

fn main2() {
    let addr = "127.0.0.1:1234".parse().unwrap();

    let f1 = TcpStream::connect(&addr)
        .and_then(|socket| {
            io::write_all(socket, b"hello world")
        })
        .and_then(|(socket, _)| {
            // read exactly 11 bytes
            io::read_exact(socket, vec![0; 11])
        })
        .and_then(|(socket, buf)| {
            println!("got {:?}", buf);
            Ok(())
        }).map_err(|e| println!("{:?}", e));

    tokio::run(f1);
}

fn main(){
    main1();
    main2();
}

常用的组合函数:

  • Change the type of a future (map, map_err)
  • Run another future after one has completed (then, and_then, or_else)
  • Figure out which of two futures resolves first (select)
  • Wait for two futures to both complete (join)
  • Convert to a trait object (Box::new)
  • Convert unwinding into errors (catch_unwind)

demo

TCP Demo:

extern crate tokio;

use tokio::prelude::*;
use tokio::io::copy;
use tokio::net::TcpListener;

fn main() {
    // Bind the server's socket.
    let addr = "127.0.0.1:12345".parse().unwrap();
    let listener = TcpListener::bind(&addr)
        .expect("unable to bind TCP listener");

    // Pull out a stream of sockets for incoming connections
    let server = listener.incoming()
        .map_err(|e| eprintln!("accept failed = {:?}", e))
        .for_each(|sock| {
            // Split up the reading and writing parts of the
            // socket.
            let (reader, writer) = sock.split();

            // A future that echos the data and returns how
            // many bytes were copied...
            let bytes_copied = copy(reader, writer);

            // ... after which we'll print what happened.
            let handle_conn = bytes_copied.map(|amt| {
                println!("wrote {:} bytes", amt.0)
            }).map_err(|err| {
                eprintln!("IO error {:?}", err)
            });

            // Spawn the future as a concurrent task.
            tokio::spawn(handle_conn)
        });

    // Start the Tokio runtime
    tokio::run(server);
}

适配器

与Iterator一样,Future,Stream和Sink trait都配备了广泛的“适配器”方法。这些方法都使用接收对象并返回一个新的包装对象。

对于future,您可以使用适配器:

  • 更改future的类型(map,map_err)
  • 一个完成后运行另一个future(then,and_then,or_else)
  • 弄清楚两个future中的哪一个先解决(select)
  • 等两个future都完成(join)
  • 转换为trait object(Box :: new)
  • 将展开转换为错误(catch_unwind)

对于流,有一大组适配器,包括:

  • 许多与Iterator有共同点,如map,fold,collect,filter,zip,take,skip等。请注意fold和collect产生future,因此它们的结果是异步计算的。
  • 用future排序的适配器(then,and_then,or_else)
  • 用于组合流的附加适配器(merge, select)

Sink trait目前具有较少的适配器

最后,可以使用拆分适配器将既是流又是接收器的对象分解为单独的流和接收对象。所有适配器都是零成本的,这意味着内部没有内存分配,并且实现将优化到您手动编写的内容。

UDP Client

extern crate futures;
extern crate tokio;

use std::env;
use std::io::stdin;
use std::net::SocketAddr;
use tokio::net::UdpSocket;
use tokio::prelude::*;

fn get_stdin_data() -> Result<Vec<u8>, Box<std::error::Error>> {
    let mut buf = String::new();
    stdin().read_line(&mut buf)?;
    Ok(buf.into_bytes())
}

fn main() -> Result<(), Box<std::error::Error>> {
    let remote_addr: SocketAddr = env::args()
        .nth(1)
        .unwrap_or("127.0.0.1:8080".into())
        .parse()?;
    // We use port 0 to let the operating system allocate an available port for us.
    let local_addr: SocketAddr = if remote_addr.is_ipv4() {
        "0.0.0.0:0"
    } else {
        "[::]:0"
    }.parse()?;
    let mut socket = UdpSocket::bind(&local_addr)?;
    const MAX_DATAGRAM_SIZE: usize = 65_507;

    let mut in_buf = Vec::new();
    loop {
        in_buf = get_stdin_data()?;
        socket = socket.send_dgram(in_buf, &remote_addr)
            .and_then(|(socket, _)| {
                socket.recv_dgram(vec![0u8; MAX_DATAGRAM_SIZE])
            })
            .map(|(s, data, len, _)| {
                println!(
                    "Received {} bytes:\n{}",
                    len,
                    String::from_utf8_lossy(&data[..len])
                );
                s
            }).wait()?;
    }
    Ok(())
}

这里的recv_dgram/send_dgram比较特殊,每次都move且构建一个socket, 所以,不能重复使用socket本身,需要在每次调用后重新为socket赋值(move)。

端口转发Demo

extern crate tokio;
extern crate futures;

use tokio::prelude::*;
use tokio::net::{TcpStream, TcpListener};
use std::net::SocketAddr;
use tokio::io;

fn main() {
    let listen_addr = "127.0.0.1:8081".parse::<SocketAddr>().unwrap();
    let server_addr = "127.0.0.1:9090".parse::<SocketAddr>().unwrap();

    println!("Listen on: {}", listen_addr.to_string());
    println!("proxy for: {}", server_addr.to_string());

    let server = TcpListener::bind(&listen_addr).unwrap()
        .incoming().map_err(|e| {
        println!("incoming error: {:?}", e);
    }).for_each(move |s| {
        println!("incoming tcp: {}", s.peer_addr().unwrap().to_string());
        let c_to_s = TcpStream::connect(&server_addr).and_then(|c| {
            let (c_r, c_w) = c.split();
            let (s_r, s_w) = s.split();
            let con1 = io::copy(c_r, s_w).and_then(|(n, r, w)| {
                io::shutdown(w).map(move |_| n)
            });
            let con2 = io::copy(s_r, c_w).and_then(|(n, r, w)| {
                io::shutdown(w).map(move |_| n)
            });
            (con1, con2)
        });
        // map like a call function
        let msg = c_to_s.map(|(c, s)| {
            println!("close! {} {}", c, s);
        }).map_err(|e| {
            println!("error: {:?}", e);
        });
        tokio::spawn(msg);
        Ok(())
    });

    tokio::run(server);
}

并行任务

extern crate tokio;
extern crate futures;

use futures::future::lazy;

fn main() {
    tokio::run(lazy(|| {
        for i in 0..4 {
            tokio::spawn(lazy(move || {
                println!("Hello from task {}", i);
                Ok(())
            }));
        }
        Ok(())
    }));
}

futures库提供了一个sync模块,这个模块包括了一些通道(channel)类型,它们是跨任务消息传递的理想选择。

  • oneshot是用于发送单个值的通道。
  • mpsc是用于发送多个值(零或多个)的通道。
extern crate tokio;
extern crate futures;

use futures::Future;
use futures::future::lazy;
use futures::sync::oneshot;

use std::{thread, time};

fn main() {
    let task = lazy(|| {
        let (tx, rx) = oneshot::channel();
        tokio::spawn(lazy(|| {
            let t = time::Duration::from_millis(3000);
            thread::sleep(t);
            tx.send("Hello,world");
            Ok(())
        }));

        rx.and_then(|msg| {
            println!("Got {}", msg);
            Ok(())
        }).map_err(|e| {
            println!("error: {:?}", e);
        }) // 注意这里没有分号,是返回值 Result
    });

    println!("run task, wait for msg!");
    tokio::run(task);
}

多条信息发送

extern crate tokio;
extern crate futures;

use futures::{Future, stream, Stream, Sink};
use futures::future::lazy;
use futures::sync::mpsc;

use std::{thread, time};

fn main() {
    let task = lazy(|| {
        let (tx, rx) = mpsc::channel(1024);
        tokio::spawn(
            stream::iter_ok(0..10).fold(tx, |tx, i| {
                let t = time::Duration::from_millis(1500);
                thread::sleep(t);
                tx.send(format!("msg from task {}", i)).map_err(|e| {
                    println!("send error: {:?}", e)
                })
            }).map(|_| ()) // 自身是返回值
        );

        rx.for_each(|msg| {
            println!("Got {}", msg);
            Ok(())
        }).map_err(|e| {
            println!("error: {:?}", e);
        }) // 注意这里没有分号,是返回值
    });

    println!("run task, wait for msg!");
    tokio::run(task);
}

tokio future

tokio future是通过poll轮训得到执行的,并且必须通过两个future配合才能完成最终的调用

#[macro_use] extern crate futures;
extern crate tokio;

use futures::{Future, Async, Poll};
use std::fmt;

struct HelloWorld;

struct Display<T>(T);

impl<T> Future for Display<T>
    where
        T: Future,
        T::Item: fmt::Display,
{
    type Item = ();
    type Error = T::Error;

    fn poll(&mut self) -> Poll<(), T::Error> {
        let value = r#try_ready!(self.0.poll());
        println!("{}", value);
        Ok(Async::Ready(()))
    }
}

impl Future for HelloWorld {
    // Item, Error是Future完成后的返回值
    type Item = String;
    type Error = ();

    // 完成状态是 Async::Ready
    // 阻塞状态是 Async::NotReady, 一般不主动返回这个状态
    // 错误状态是 Error
    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
        Ok(Async::Ready("Hello, world!".to_string()))
    }
}

fn main(){
    let f = Display(HelloWorld);
    tokio::run(f);
}

配合其他Future来完成

extern crate tokio;
#[macro_use] extern crate futures;

use tokio::net::{TcpStream, tcp::ConnectFuture};
use futures::{Future, Async, Poll};

struct GetPeerAddr {
    connect: ConnectFuture,
}

impl Future for GetPeerAddr {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        match self.connect.poll() {
            Ok(Async::Ready(socket)) => {
                println!("peer address = {}", socket.peer_addr().unwrap());
                Ok(Async::Ready(()))
            }
            Ok(Async::NotReady) => Ok(Async::NotReady),
            Err(e) => {
                println!("failed to connect: {}", e);
                Ok(Async::Ready(()))
            }
        }
    }
}

fn main() {
    let addr = "192.168.0.1:1234".parse().unwrap();
    let connect_future = TcpStream::connect(&addr);
    let get_peer_addr = GetPeerAddr {
        connect: connect_future,
    };

    tokio::run(get_peer_addr);
}

tokio stream

Stream类似Future,但是可以持续产生不同的值,可以看作是future的异步产生器。

#[macro_use] extern crate futures;

use futures::{Future, Stream, Poll, Async};
use std::fmt;


pub struct Fibonacci {
    curr: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Fibonacci {
        Fibonacci {
            curr: 1,
            next: 1,
        }
    }
}

impl Stream for Fibonacci {
    type Item = u64;

    // The stream will never yield an error
    type Error = ();

    fn poll(&mut self) -> Poll<Option<u64>, ()> {
        let curr = self.curr;
        let next = curr + self.next;

        self.curr = self.next;
        self.next = next;

        Ok(Async::Ready(Some(curr)))
    }
}

pub struct Display10<T> {
    stream: T,
    curr: usize,
}

impl<T> Display10<T> {
    fn new(stream: T) -> Display10<T> {
        Display10 {
            stream,
            curr: 0,
        }
    }
}

impl<T> Future for Display10<T>
    where
        T: Stream,
        T::Item: fmt::Display,
{
    type Item = ();
    type Error = T::Error;

    fn poll(&mut self) -> Poll<(), Self::Error> {
        while self.curr < 10 {
            let value = match try_ready!(self.stream.poll()) {
                Some(value) => value,
                // There were less than 10 values to display, terminate the
                // future.
                None => break,
            };

            println!("value #{} = {}", self.curr, value);
            self.curr += 1;
        }

        Ok(Async::Ready(()))
    }
}

fn main() {
    let fib = Fibonacci::new();
    let display = Display10::new(fib);
    tokio::run(display);
}

stream进阶版

extern crate futures;

use futures::{stream, Stream};

fn fibonacci() -> impl Stream<Item = u64, Error = ()> {
    stream::unfold((1, 1), |(curr, next)| {
        let new_next = curr + next;

        Some(Ok((curr, (next, new_next))))
    })
}

fn main(){
    tokio::run(
        fibonacci().take(10)
            .for_each(|num| {
                println!("{}", num);
                Ok(())
            })
    );
}

其中, unfold将一个初始值,展开为多个值(流)。fold是将多个值,合拢为一个值。

stream本身就是Result<Option<Item, e>>类型。

  • Change the type of a stream (map, map_err, and_then).
  • Handle stream errors (or_else).
  • Filter stream values (take, take_while, skip, skip_while, filter, filter_map).
  • Asynchronously iterate the values (for_each, fold).
  • Combine multiple streams together (zip, chain, select).

定制stream

extern crate tokio;
#[macro_use]
extern crate futures;

use tokio::io;
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use std::net::SocketAddr;
use futures::lazy;
use std::io::Error;
use std::{thread, time};

struct MyTcpSender {
    stream: TcpStream,
    count: i32,
}

impl Future for MyTcpSender {
    type Item = ();
    type Error = Error;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let d = time::Duration::from_millis(500);
        let mut i = 0;
        while i < self.count {
            let msg = format!("msg {}", i);
            // try_ready! 很关键
            try_ready!(self.stream.poll_write(msg.as_bytes()));
//            self.stream.write_all(msg.as_bytes())?;
            thread::sleep(d);
            i += 1;
        }
        Ok(Async::Ready(()))
    }
}

fn main() {
    let addr = "0.0.0.0:8888";
    let addr_s = addr.parse::<SocketAddr>().unwrap();
    let listener = TcpListener::bind(&addr_s).map_err(|e| {
        println!("bind error: {:?}", e)
    }).unwrap();
    let task = listener.incoming().for_each(|stream| {
        println!("======");
//        let m = io::write_all(stream, "21321").map(|_| println!("")).map_err(|e| println!(""));
        let m = MyTcpSender { stream: stream, count: 100 }.map(|_| ()).map_err(|_| ());
        tokio::spawn(m);
        println!("-----");
        Ok(())
    }).map_err(|e| {
        println!("incomming error: {:?}", e)
    });
    tokio::run(task);
}

猜你喜欢

转载自blog.csdn.net/bbdxf/article/details/87890145