0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Tokio 的基本用法

科技綠洲 ? 來源:TinyZ ? 作者:TinyZ ? 2023-09-19 16:05 ? 次閱讀

Tokio 是一個異步 I/O 框架,它提供了一種高效的方式來編寫異步代碼。它使用 Rust 語言的 Futures 庫來管理異步任務(wù),并使用 Reactor 模式來處理 I/O 事件。

本系列 Tokio 篇將由淺入深的從基礎(chǔ)到實戰(zhàn),以一個完整的 Rust 語言子系列講述網(wǎng)絡(luò)編程

為什么要使用 Tokio?

在 Rust 中,使用異步編程可以提高程序的性能和響應(yīng)速度,但是異步編程往往需要編寫大量的樣板代碼和復(fù)雜的控制流程。Tokio 提供了一種簡單的方式來編寫異步代碼,它使用 Futures 庫來管理異步任務(wù),并提供了一組工具來處理異步 I/O 事件。

如何使用 Tokio?

使用 Tokio 編寫異步代碼需要掌握以下幾個概念:

  • ? Future:表示一個異步任務(wù),可以理解為一個異步函數(shù)的返回值;
  • ? Task:表示一個異步任務(wù)的執(zhí)行上下文,可以理解為一個異步函數(shù)的執(zhí)行環(huán)境;
  • ? Reactor:表示一個 I/O 事件的處理器,可以理解為一個事件循環(huán);
  • ? Runtime:表示一個異步任務(wù)的執(zhí)行環(huán)境,可以理解為一個異步函數(shù)的運行時環(huán)境。

下面我們將使用 Tokio 編寫一個最基礎(chǔ)的服務(wù)器和客戶端程序,以便了解 Tokio 的基本用法。

編寫服務(wù)器

我們將編寫一個簡單的 PingPong 服務(wù)器,它接收客戶端的 Ping 請求,并返回 Pong 響應(yīng)。首先,我們需要創(chuàng)建一個異步任務(wù)來處理客戶端的請求。我們可以使用 Tokio 提供的async關(guān)鍵字來定義一個異步函數(shù):

async fn handle_client(mut stream: TcpStream) - > Result< (), Box< dyn Error >> {
    // ...
}

這個異步函數(shù)接收一個TcpStream對象,表示一個客戶端連接。我們可以在函數(shù)內(nèi)部處理客戶端的請求,并返回一個Result對象表示異步任務(wù)的執(zhí)行結(jié)果。在處理客戶端請求之前,我們需要先向客戶端發(fā)送一個歡迎消息:

async fn handle_client(mut stream: TcpStream) - > Result< (), Box< dyn Error >> {
    println!("new client connected");

    let mut buf = [0; 1024];
    stream.write_all(b"Welcome to the PingPong server!n").await?;

    // ...
}

在發(fā)送歡迎消息之后,我們需要不斷地從客戶端讀取數(shù)據(jù),并返回 Pong 響應(yīng)。我們可以使用一個無限循環(huán)來實現(xiàn)這個功能:

async fn handle_client(mut stream: TcpStream) - > Result< (), Box< dyn Error >> {
    println!("new client connected");

    let mut buf = [0; 1024];
    stream.write_all(b"Welcome to the PingPong server!n").await?;

    loop {
        let n = stream.read(&mut buf).await?;
        if n == 0 {
            break;
        }
        stream.write_all(b"Pongn").await?;
    }

    println!("client disconnected");
    Ok(())
}

在循環(huán)中,我們使用stream.read()方法從客戶端讀取數(shù)據(jù),并使用stream.write_all()方法向客戶端發(fā)送 Pong 響應(yīng)。如果客戶端關(guān)閉了連接,我們就退出循環(huán)并返回Ok(())表示異步任務(wù)執(zhí)行成功。

現(xiàn)在我們已經(jīng)編寫了一個異步任務(wù)來處理客戶端請求,接下來我們需要創(chuàng)建一個 Reactor 來處理 I/O 事件。我們可以使用 Tokio 提供的TcpListener對象來監(jiān)聽客戶端連接:

#[tokio::main]
async fn main() - > Result< (), Box< dyn Error >> {
    let addr = "127.0.0.1:8080";
    let listener = TcpListener::bind(addr).await?;
    println!("listening on {}", addr);

    loop {
        let (stream, _) = listener.accept().await?;
        tokio::spawn(async move {
            if let Err(e) = handle_client(stream).await {
                eprintln!("error: {}", e);
            }
        });
    }
}

main函數(shù)中,我們首先創(chuàng)建一個TcpListener對象來監(jiān)聽客戶端連接。然后我們使用一個無限循環(huán)來等待客戶端連接,并使用listener.accept()方法來接收客戶端連接。當(dāng)有新的客戶端連接時,我們就創(chuàng)建一個新的異步任務(wù)來處理客戶端請求,并使用tokio::spawn()方法將任務(wù)提交到 Reactor 中執(zhí)行。

現(xiàn)在我們已經(jīng)完成了一個最基礎(chǔ)的 PingPong 服務(wù)器,可以使用cargo run命令來運行程序,并使用 telnet 命令來測試服務(wù)器:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/pingpong`
listening on 127.0.0.1:8080
$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the PingPong server!
ping
Pong
ping
Pong
^]
telnet > quit
Connection closed.

編寫客戶端

現(xiàn)在我們已經(jīng)編寫了一個最基礎(chǔ)的 PingPong 服務(wù)器,接下來我們將編寫一個客戶端程序來連接服務(wù)器并發(fā)送 Ping 請求。首先,我們需要創(chuàng)建一個異步任務(wù)來連接服務(wù)器:

async fn connect() - > Result< (), Box< dyn Error >> {
    let addr = "127.0.0.1:8080";
    let mut stream = TcpStream::connect(addr).await?;
    println!("connected to {}", addr);

    // ...
}

這個異步任務(wù)使用TcpStream::connect()方法來連接服務(wù)器,并返回一個Result對象表示連接結(jié)果。在連接成功之后,我們可以向服務(wù)器發(fā)送一個 Ping 請求:

async fn connect() - > Result< (), Box< dyn Error >> {
    let addr = "127.0.0.1:8080";
    let mut stream = TcpStream::connect(addr).await?;
    println!("connected to {}", addr);

    stream.write_all(b"Pingn").await?;
    let mut buf = [0; 1024];
    let n = stream.read(&mut buf).await?;
    let pong = std::str::from_utf8(&buf[..n])?;
    println!("{}", pong);

    Ok(())
}

在發(fā)送 Ping 請求之后,我們使用stream.read()方法從服務(wù)器讀取響應(yīng),并使用std::str::from_utf8()方法將響應(yīng)轉(zhuǎn)換為字符串。最后,我們將響應(yīng)打印到控制臺上,并返回Ok(())表示異步任務(wù)執(zhí)行成功。

現(xiàn)在我們已經(jīng)編寫了一個異步任務(wù)來連接服務(wù)器并發(fā)送 Ping 請求,接下來我們需要在main函數(shù)中啟動這個任務(wù):

#[tokio::main]
async fn main() - > Result< (), Box< dyn Error >> {
    connect().await?;
    Ok(())
}

現(xiàn)在我們已經(jīng)完成了一個最基礎(chǔ)的 PingPong 客戶端,可以使用cargo run命令來運行程序,并查看控制臺輸出:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/pingpong`
connected to 127.0.0.1:8080
Pong

完整代碼

最后,我們將完整的服務(wù)器和客戶端代碼放在一起,以便讀者參考:

use std::error::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

async fn handle_client(mut stream: TcpStream) - > Result< (), Box< dyn Error >> {
    println!("new client connected");

    let mut buf = [0; 1024];
    stream.write_all(b"Welcome to the PingPong server!n").await?;

    loop {
        let n = stream.read(&mut buf).await?;
        if n == 0 {
            break;
        }
        stream.write_all(b"Pongn").await?;
    }

    println!("client disconnected");
    Ok(())
}

#[tokio::main]
async fn main() - > Result< (), Box< dyn Error >> {
    let addr = "127.0.0.1:8080";
    let listener = TcpListener::bind(addr).await?;
    println!("listening on {}", addr);

    loop {
        let (stream, _) = listener.accept().await?;
        tokio::spawn(async move {
            if let Err(e) = handle_client(stream).await {
                eprintln!("error: {}", e);
            }
        });
    }
}

async fn connect() - > Result< (), Box< dyn Error >> {
    let addr = "127.0.0.1:8080";
    let mut stream = TcpStream::connect(addr).await?;
    println!("connected to {}", addr);

    stream.write_all(b"Pingn").await?;
    let mut buf = [0; 1024];
    let n = stream.read(&mut buf).await?;
    let pong = std::str::from_utf8(&buf[..n])?;
    println!("{}", pong);

    Ok(())
}

#[tokio::main]
async fn main() - > Result< (), Box< dyn Error >> {
    connect().await?;
    Ok(())
}

總結(jié)

通過本文的介紹,我們了解了 Tokio 的基本用法,并編寫了一個最基礎(chǔ)的 PingPong 服務(wù)器和客戶端程序。Tokio 提供了一種簡單的方式來編寫異步代碼,可以幫助我們提高程序的性能和響應(yīng)速度。在實際開發(fā)中,我們可以根據(jù)需要使用 Tokio 提供的各種工具來編寫更加復(fù)雜的異步程序。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 程序
    +關(guān)注

    關(guān)注

    116

    文章

    3762

    瀏覽量

    80757
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4723

    瀏覽量

    68237
  • 網(wǎng)絡(luò)編程
    +關(guān)注

    關(guān)注

    0

    文章

    68

    瀏覽量

    10055
  • Tokio
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    50
收藏 人收藏

    評論

    相關(guān)推薦

    SQLx的基礎(chǔ)用法和進(jìn)階用法

    SQLx是一個Rust語言的異步SQL數(shù)據(jù)庫訪問庫,支持多種數(shù)據(jù)庫,包括PostgreSQL、MySQL、SQLite等。本教程將以SQLite為例,介紹SQLx的基礎(chǔ)用法和進(jìn)階用法。 基礎(chǔ)用法
    的頭像 發(fā)表于 09-19 14:29 ?2214次閱讀

    SQLx在Rust語言中的基礎(chǔ)用法和進(jìn)階用法

    SQLx是一個Rust語言的異步SQL執(zhí)行庫,它支持多種數(shù)據(jù)庫,包括MySQL、PostgreSQL、SQLite等。本教程將以MySQL數(shù)據(jù)庫為例,介紹SQLx在Rust語言中的基礎(chǔ)用法和進(jìn)階用法
    的頭像 發(fā)表于 09-19 14:32 ?4972次閱讀

    Stream模塊的基礎(chǔ)用法和進(jìn)階用法

    在 Rust 語言中,Tokio 是一個非常流行的異步編程框架。它提供了一系列的模塊,其中最常用的就是 Stream 模塊。Stream 模塊允許我們以異步的方式處理數(shù)據(jù)流,這在很多情況下非常
    的頭像 發(fā)表于 09-19 15:33 ?1133次閱讀

    什么是Tokio模塊 Channel?

    Rust 語言是一種系統(tǒng)級編程語言,它具有強(qiáng)類型和內(nèi)存安全性。Rust 語言中的 Tokio 模塊是一個異步編程庫,它提供了一種高效的方式來處理異步任務(wù)。其中,channel 是 Tokio 模塊中
    的頭像 發(fā)表于 09-19 15:57 ?902次閱讀

    AsyncRead和AsyncWrite 模塊進(jìn)階用法示例

    們。 基礎(chǔ)用法 從文件中讀取數(shù)據(jù) use tokio::fs::File; use tokio::io::{ self , AsyncReadExt}; #[tokio:
    的頭像 發(fā)表于 09-20 11:41 ?820次閱讀

    狀態(tài)機(jī)原理及用法

    狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法
    發(fā)表于 03-15 15:25 ?0次下載

    使用tokio實現(xiàn)一個簡單的Client和Server通訊模型

    本系列是關(guān)于用Rust構(gòu)建一個KV Server的系列文章,內(nèi)容包括用tokio做底層異步網(wǎng)絡(luò)通訊、使用toml文件做配置、protobuf做傳輸協(xié)議、內(nèi)存/RockDB做數(shù)據(jù)存儲、事件通知、優(yōu)雅關(guān)機(jī)、并發(fā)連接限制及測量監(jiān)控等。
    的頭像 發(fā)表于 09-09 09:45 ?2215次閱讀

    WasmEdge增加了Tokio支持

    看:https://wasmer.io/posts/wasmer-takes-webassembly-libraries-manistream-with-wai WasmEdge增加了Tokio 支持
    的頭像 發(fā)表于 12-05 11:55 ?790次閱讀

    Tokio中hang死所有worker的方法

    原因是 tokio 里的待執(zhí)行 task 不是簡單的放到一個 queue 里,除了 runtime 內(nèi)共享的,可被每個 worker 消費的run_queue[2],每個 worker 還有一個自己的 lifo_slot[3],只存儲一個最后被放入的 task (目的是減小調(diào)度延遲)。
    的頭像 發(fā)表于 02-03 16:26 ?938次閱讀

    文盤Rust -- 用Tokio實現(xiàn)簡易任務(wù)池

    59執(zhí)行完后面就沒有輸出了,如果把max_task設(shè)置為2,情況會好一點,但是也沒有執(zhí)行完所有的異步操作,也就是說在資源不足的情況下,Tokio會拋棄某些任務(wù),這不符合我們的預(yù)期。
    的頭像 發(fā)表于 04-09 10:24 ?1256次閱讀

    Tokio 模塊的優(yōu)雅停機(jī)機(jī)制

    在進(jìn)行高并發(fā)、網(wǎng)絡(luò)編程時,優(yōu)雅停機(jī)是一個非常重要的問題。在 Rust 語言中,Tokio 是一個非常流行的異步編程框架,它提供了一些優(yōu)雅停機(jī)的機(jī)制,本文將圍繞 Tokio 模塊的優(yōu)雅停機(jī)進(jìn)行詳細(xì)
    的頭像 發(fā)表于 09-19 15:26 ?562次閱讀

    如何使用Tokio 和 Tracing模塊構(gòu)建異步的網(wǎng)絡(luò)應(yīng)用程序

    在 Rust 語言中,Tokio 是一個非常流行的異步運行時,它提供了高效的異步 I/O 操作和任務(wù)調(diào)度。而 Tracing 則是一個用于應(yīng)用程序跟蹤的框架,它可以幫助我們理解應(yīng)用程序的行為和性能
    的頭像 發(fā)表于 09-19 15:29 ?614次閱讀

    基于select!宏的進(jìn)階用法

    Tokio 是一個基于 Rust 語言的異步編程框架,它提供了一組工具和庫,使得異步編程變得更加容易和高效。其中最重要的組件之一就是 select!宏。 select!宏是 Tokio 中的一個核心
    的頭像 發(fā)表于 09-19 15:35 ?582次閱讀

    如何使用 Tokio 模塊的Channel

    Channel 是一種在多線程環(huán)境下進(jìn)行通信的機(jī)制,可以讓線程之間互相發(fā)送消息和共享數(shù)據(jù)。Rust 語言中的 Tokio 模塊提供了一種異步的 Channel 實現(xiàn),使得我們可以在異步程序中方
    的頭像 發(fā)表于 09-19 15:38 ?619次閱讀

    tokio模塊channel中的使用場景和優(yōu)缺點

    Rust 語言的 tokio 模塊提供了一種高效的異步編程方式,其中的 channel 模塊是其核心組件之一。本教程將介紹 tokio 模塊 channel 的除了上文提到的 mspc
    的頭像 發(fā)表于 09-19 15:54 ?717次閱讀