async是什么
async
是 Rust 选择的异步编程模型
block_on()----执行器
async fn do_something(){
println!("go,go,go");
}
fn main() {
do_something();
}
use futures::executor::block_on;
async fn do_something(){
println!("go,go,go");
}
fn main() {
let future=do_something();
block_on(future);
}
状态机
调用非执行
async标记的函数(代码)是一个状态机,当调用这个函数时,并不是执行者函数,只是返回这个状态机的futures特征
执行futures
block_on()------.await
block_on()--->阻塞
.await--->并发
在async fn
函数中使用.await
可以等待另一个异步调用的完成。但是与block_on
不同,.await
并不会阻塞当前的线程,而是异步的等待Future A
的完成,在等待的过程中,该线程还可以继续执行其它的Future B
,最终实现了并发处理的效果。
.await的作用
.await不会发起像block_on()一样让线程阻塞的操作,但是也不是说,可以让.await之后的代码可以.await一起运行,而是也要等到.await这一行代码运行完,之后的代码才能运行。
use std::time::Duration;
use std::thread::sleep;
use futures::executor::block_on;
async fn do_first(){
sleep(Duration::from_secs(10));
println!("do_first");
}
async fn do_second(){
println!("do_second");
}
async fn goto_do(){
do_first().await;
do_second().await;
}
fn main(){
let m_future=goto_do();
block_on(m_future);
}
.await的使用需要先引入一下包:
[dependencies]
futures = "0.3"
代码块什么时候返回
block_on执行future之后才会返回函数的返回值
block_on嵌套block_on不行:
use futures::executor::block_on;
async fn do_something(){
//do_another().await;//异步执行
let future=do_another();
block_on(future);
println!("go,go,go");
}
async fn do_another(){
println!("do,do,do");
}
fn main() {
let future=do_something();
block_on(future);//阻塞当前线程
}
代码奔溃
因为这里只有一个线程,想要两次阻塞是不可能的。
awiat作用范围
只允许在async标识的函数或者代码块内部使用。
Future
需要被执行器poll
(轮询)后才能运行,
Future
并不能保证在一次 poll
中就被执行完,
当未来 Future
准备好进一步执行时, 该函数会被调用去通知执行器,这个Future准备好了,
(这个同C++的epoll,通过回调,而非轮询的方式)
(考虑一个需要从 socket
读取数据的场景:如果有数据,可以直接读取数据并返回 Poll::Ready(data)
, 但如果没有数据,Future
会被阻塞且不会再继续执行,此时它会注册一个 wake
函数,当 socket
数据准备好时,该函数将被调用以通知执行器:我们的 Future
已经准备好了,可以继续执行。)
最外层的 async
函数,执行器 executor
推动它们运行,
几个重要的概念
async
async
是 Rust 选择的异步编程模型;
异步编程使用async fn
语法来创建一个异步函数;
有两种方式可以使用async
: async fn
用于声明函数,async { ... }
用于声明语句块,它们会返回一个实现 Future
特征的值;
.await
Future
Future是一个特征,返回的Future是返回实现了这个Future的特征的实例对象。
future运行的方法
async
是懒惰的,直到被执行器 poll
或者 .await
后才会开始运行,其中后者是最常用的运行 Future
的方法。
waker的wake函数
Future
将从 Socket
读取数据,若当前还没有数据,则会让出当前线程的所有权,允许执行器去执行其它的 Future
。当数据准备好后,会调用 wake()
函数将该 Future
的任务放入任务通道中,等待执行器的 poll
。
执行器
pin和Unpin
问题:本来指向的地址的值被移动了
Tokio服务编程
模块
use tokio::net::{TcpListener, TcpStream};
#[tokio::main]宏
对于main函数,tokio提供了简化的异步运行时创建方式:
main函数默认不可以async fn修饰为异步函数,但是可以用#[tokio::main]宏代替fm main.
网络编程步骤
1,引入tokio模块
在正式开始学习tokio之前,当然是在Cargo.toml中引入tokio。在Cargo.toml文件中添加以下依赖:
// 开启全部功能的tokio, // 在了解tokio之后,只开启需要的特性,减少编译时间,减小编译大小 tokio = {version = "1.4", features = ["full"]}
创建连接的方法
2-1:最简单的---->绑定,接收就完成
server端---绑定并接收
TcpListener代表服务端套接字,可使用bind()方法指定要绑定的地址,bind()之后再await,即可开始监听。
async fn main(){
let listener = TcpListener::bind("127.0.0.1:8888").await.unwrap();
loop {
let (socktfd, client_sock_addr) = listener.accept().await.unwrap();
tokio::spawn(async move {
// 该任务负责处理client
});
}
}
这里的listener代表的是服务端负责监听的套接字。
client端----connect
use tokio::net::TcpStream;
#![allow(unused)]
fn main() {
let mut socketfd = TcpStream::connect("127.0.0.1:8080").await.unwrap();
}
以上,无论是服务端还是客户端的socketfd,套接字的类型都是TcpStream