Tokio 비동기 녹슨 기초 지식

25375 단어 tutorialrusttokioasync

소개하다.


비동기 코드가 지금 없는 곳이 없습니다.API/백엔드 프로그램과 같은 확장이 필요한 모든 것을 쓸 때, 이것은 기본적으로 필수적이다.Rust의 비동기 코드를 처리하는 방법은 다르지만 Tokio라고 부르는 가장 유행하는 쪽지 상자를 사용할 것입니다.결국 여러 요청을 동시에 처리할 수 있는 매우 간단한 API를 만들 것입니다.우리 시작합시다!
마지막 코드는 여기에서 찾을 수 있습니다: Tokio tutorial Github repo.

단순 비동기


우선 임무를 수행하는 기본 프로그램을 만듭니다.프로젝트를 만들려면 다음과 같이 하십시오.
$ cargo new tokio-tutorial
$ cd tokio-tutorial
이 임무에 대해 우리는 단지 의존항 하나만 필요로 한다.그래서 Cargo.toml를 열고 다음과 같이 추가합니다.
[dependencies]
tokio = {version = "1.14.0", features = ["full"]}
이제 src/main.rs로 이동하여 내용을 다음으로 바꿉니다.
#[tokio::main]
async fn main() {
    let task = tokio::spawn(async {
        println!("Hello, rust!");
    });

    task.await.unwrap();
}
이것이 바로 cargo run 간단한 비동기 작업을 실행하는 데 필요한 전부입니다!
이 장의 전체 코드on my Github를 찾을 수 있습니다.
물론 이 예는 Tokio가 실행될 때의 진정한 위력을 보여주지 않았기 때문에 더 유용한 예로 넘어가도록 하겠습니다.

저축 잔액 API


좋습니다. 다른 업무 목록 API를 만드는 것을 피하기 위해서, 저축 잔액 API를 더 간단하게 할 것입니다.목표는 간단합니다. GET와 POST로 균형을 관리하는 두 가지 방법을 보여 드리겠습니다.GET는 현재 값을 반환하고 POST는 가/감 작업을 수행합니다.만약 네가 녹슨 책을 뒤적거린다면, 너는 우연히 발견할 수 있을 것이다. Multithreaded Web Server project이것은 당신의 머리를 라인을 둘러싸게 하는 아주 좋은 출발점이지만, 대량의 샘플 코드 (수동 라인 관리 등) 가 필요하다.이것이 바로 Tokio의 용무지이다.

제1반응


우리는 전송 요청을 감청하는 간단한 서버부터 시작할 것이다.main.rs의 컨텐트를 다음으로 바꾸려면 다음과 같이 하십시오.
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:8181").await.unwrap();

    loop {
        let (stream, _) = listener.accept().await.unwrap();
        handle_connection(stream).await;
    }
}

async fn handle_connection(mut stream: TcpStream) {
    let contents = "{\"balance\": 0.00}";

    let response = format!(
        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
        contents.len(),
        contents
    );
    stream.write(response.as_bytes()).await.unwrap();
    stream.flush().await.unwrap();
}
그것을 실행한 다음 브라우저에서 http://127.0.0.1:8181 로 이동하여 서버의 첫 번째 응답을 보십시오.몇 가지 코드 설명:
  • TCP 탐지기가 로컬 주소로 생성되어 바인딩되었습니다.
  • 순환 중에 우리는 전송된 연결을 기다린다.
  • 연결이 되면 처리 프로그램에 흐름을 전달합니다
  • 좋아, 하지만 이것은 다중 임무가 아니야!
    예를 들어, 우리의 코드는 한 번에 하나의 요청만 처리한다.그러면 우리는 어떻게 그것으로 하여금 연결을 동시에 처리하게 합니까?아주 간단합니다.handle_connection()tokio::spawn 함수에 포장하기만 하면 됩니다.
    tokio::spawn(async move {
        handle_connection(stream).await;
    });
    
    이렇게!이제 여러 연결을 한 번에 처리할 수 있습니다!
    지금까지의 코드Can be found on GitHub here

    가져오기 및 게시


    우리가 강좌의 마지막 부분에 들어간다. 균형치를 수정하기 전에, 우리는 우리가 균형을 읽고 변경할 수 있도록 확보해야 한다.
    간단하게 보기 위해서 우리는 두 가지 장면이 있을 것이다.
  • 획득http://127.0.0.1:8181
    GET 요청이 감지되면 잔액이 반환됩니다.
  • 우편http://127.0.0.1:8181/62.32
    POST 메서드가 라우팅에서 값(최대 10자)을 읽고 잔액을 업데이트하고 반환합니다.
  • 물론 이것은 가장 RESTful하거나 신축 가능한 방법은 아니지만 본 강좌에서 효과가 매우 좋다.
    새로운 모델handle_connection의 모양은 다음과 같습니다.
    async fn handle_connection(mut stream: TcpStream) {
        // Read the first 16 characters from the incoming stream.
        let mut buffer = [0; 16];
        stream.read(&mut buffer).await.unwrap();
        // First 4 characters are used to detect HTTP method
        let method_type = match str::from_utf8(&buffer[0..4]) {
            Ok(v) => v,
            Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
        };
    
        let contents = match method_type {
            "GET " => {
                // todo: return real balance
                format!("{{\"balance\": {}}}", 0.0)
            }
            "POST" => {
                // Take characters after 'POST /' until whitespace is detected.
                let input: String = buffer[6..16]
                    .iter()
                    .take_while(|x| **x != 32u8)
                    .map(|x| *x as char)
                    .collect();
                let balance_update = input.parse::<f32>().unwrap();
                // todo: add balance update handling
                format!("{{\"balance\": {}}}", balance_update)
            }
            _ => {
                panic!("Invalid HTTP method!")
            }
        };
    
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
            contents.len(),
            contents
        );
        stream.write(response.as_bytes()).await.unwrap();
        stream.flush().await.unwrap();
    }
    
    전송된 요청에서 n 개의 문자를 읽고 선택한 동작을 실행하는 것이 우리의 생각입니다.프레젠테이션의 목적과 단순성을 위해 최대 10자까지 입력할 것입니다.
    지금 그것을 실행해 보세요. 응답이 선택한 방법에 따라 어떻게 변화하는지 보십시오.예제 cURL 명령:
    curl --request POST 'http://127.0.0.1:8181/-12.98'
    

    평형


    지금까지, 우리의 처리 프로그램은 하드코딩의 결과를 되돌려 주었다.호출 사이에서 값을 유지할 변수를 도입합시다.우리의 균형 변수는 작업과 잠재적인 라인 사이에서 공유되기 때문에 이를 지원하기 위해 Arc<Mutex<_>>에 포장되어 있습니다.Arc<>는 여러 작업에서 변수를 동시에 인용할 수 있도록 허용한다Mutex<>는 보호로서 한 번에 한 작업만 변수를 변경할 수 있도록 확보한다(다른 작업은 그것들의 차례를 기다려야 한다).
    다음은 잔액 업데이트의 전체 코드입니다.새 행의 주석을 보려면 다음과 같이 하십시오.
    use std::str;
    use std::sync::{Arc, Mutex, MutexGuard};
    use tokio::io::AsyncReadExt;
    use tokio::io::AsyncWriteExt;
    use tokio::net::{TcpListener, TcpStream};
    
    #[tokio::main]
    async fn main() {
        // create balance wrapped in Arc and Mutex for cross thread safety
        let balance = Arc::new(Mutex::new(0.00f32));
        let listener = TcpListener::bind("127.0.0.1:8181").await.unwrap();
    
        loop {
            let (stream, _) = listener.accept().await.unwrap();
            // Clone the balance Arc and pass it to handler
            let balance = balance.clone();
            tokio::spawn(async move {
                handle_connection(stream, balance).await;
            });
        }
    }
    
    async fn handle_connection(mut stream: TcpStream, balance: Arc<Mutex<f32>>) {
        // Read the first 16 characters from the incoming stream.
        let mut buffer = [0; 16];
        stream.read(&mut buffer).await.unwrap();
        // First 4 characters are used to detect HTTP method
        let method_type = match str::from_utf8(&buffer[0..4]) {
            Ok(v) => v,
            Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
        };
        let contents = match method_type {
            "GET " => {
                // before using balance we need to lock it.
                format!("{{\"balance\": {}}}", balance.lock().unwrap())
            }
            "POST" => {
                // Take characters after 'POST /' until whitespace is detected.
                let input: String = buffer[6..16]
                    .iter()
                    .take_while(|x| **x != 32u8)
                    .map(|x| *x as char)
                    .collect();
                let balance_update = input.parse::<f32>().unwrap();
    
                // acquire lock on our balance and update the value
                let mut locked_balance: MutexGuard<f32> = balance.lock().unwrap();
                *locked_balance += balance_update;
                format!("{{\"balance\": {}}}", locked_balance)
            }
            _ => {
                panic!("Invalid HTTP method!")
            }
        };
    
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
            contents.len(),
            contents
        );
        stream.write(response.as_bytes()).await.unwrap();
        stream.flush().await.unwrap();
    }
    
    잔액 자물쇠를 가져오고 있지만 수동으로 잠금을 풀 필요는 없습니다.일단 MutexGuard가 되돌아온 lock()가 범위를 초과하면 자물쇠는 자동으로 제거됩니다.우리의 경우, 이것은 우리가 응답 내용을 준비한 후이다.
    현재, 우리는 프로그램을 실행할 준비가 되어 있으며, 우리가 정확한 균형 응답을 받았는지 테스트할 준비가 되어 있다.예제 cURL 명령:
    curl --request POST 'http://127.0.0.1:8181/150.50'
    
    다음은 연습입니다: lock을 사용하여 코드의 실행에 어떻게 영향을 미치는지 이해하려면 지연을 추가하십시오 (보십시오 tokio::timer::Delay). 자물쇠를 얻은 후에 지연이 충분하면 여러 개의 API 호출을 할 수 있습니다.

    결론


    보시다시피 Rust에서 비동기적인 조작을 처리하는 프로그램을 만드는 것이 Tokio runtime보다 쉽습니다.물론 API에 대해서는 Actix, 로켓, Warp 등 웹 프레임워크를 사용하지만, 어떻게 엔진 뚜껑에서 작동하는지 더 잘 이해하기를 바랍니다.
    마지막 코드는 여기에서 찾을 수 있습니다: Tokio tutorial Github repo.
    나는 당신이 이 강좌를 좋아하길 희망합니다. 만약 어떤 건의/문제가 있으면 언제든지 아래에서 평론을 발표하십시오.
    다음까지 읽어주셔서 감사합니다!

    좋은 웹페이지 즐겨찾기