rust學習~tokio的io

await

Suspend execution until the result of a Future is ready.
暫停執行,直到一個 Future 的結果就緒。

.awaiting a future will suspend the current function’s execution until the executor has run the future to completion.
對一個 Future 使用 .await 操作會暫停當前函數的執行,直到執行器(executor)將該 Future 運行至完成

Read the async book for details on how async/await and executors work.
有關異步 / 等待(async/await)和執行器的工作原理的詳細信息,請閱讀《異步編程指南》(Async Book)。

Editions
await is a keyword from the 2018 edition onwards.
await 是從 2018 版及后續版本開始引入的關鍵字。

It is available for use in stable Rust from version 1.39 onwards.
從 1.39 版本及以后的穩定版 Rust 中可以使用它。

AsyncReadExt

use tokio::io::{self, AsyncReadExt};
use tokio::fs::File;#[tokio::main]
async fn main() -> io::Result<()> {let mut f = File::open("foo.txt").await?;let mut buffer = Vec::new();// 讀取整個文件的內容f.read_to_end(&mut buffer).await?;// String::from_utf8 是 String 類型的一個關聯函數// 專門用于把 Vec<u8> 類型的字節向量轉換為 String 類型的 UTF - 8 字符串// 它會檢查字節向量中的字節序列是否符合 UTF - 8 編碼規則// 如果符合則返回一個 Ok(String),若不符合則返回 Err(FromUtf8Error)// 適用于從字節數據(如文件讀取、網絡接收等)構建字符串,并且需要確保數據是有效的 UTF - 8 編碼match String::from_utf8(buffer) {Ok(content) => {println!("文件內容如下:\n{}", content);}Err(e) => {eprintln!("將文件內容轉換為字符串時出錯: {}", e);}}Ok(())
}
use tokio::io::{self, AsyncReadExt};
use tokio::fs::File;#[tokio::main]
async fn main() -> io::Result<()> {let mut f = File::open("foo.txt").await?;let mut buffer = Vec::new();// 讀取整個文件的內容f.read_to_end(&mut buffer).await?;Ok(())
}

為什么說 字節數組 &[u8] 實現了 AsyncRead ?

字節數組切片 &[u8] 實現了 AsyncRead 特征,這意味著它可以作為異步讀取操作的數據源,允許以異步的方式從字節數組中讀取數據

AsyncRead 特征的作用

AsyncRead 是 tokio 異步運行時庫中定義的一個特征,它定義了異步讀取操作的接口。其核心方法是 poll_read,該方法用于嘗試從數據源中異步讀取數據到指定的緩沖區。通過實現 AsyncRead 特征,類型可以參與到異步 I/O 操作中,利用異步運行時的調度機制,在等待數據可讀時讓出控制權,提高程序的并發性能。

AsyncRead 特征的簡化定義如下:

use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::Result;pub trait AsyncRead {fn poll_read(self: Pin<&mut Self>,cx: &mut Context<'_>,buf: &mut [u8],) -> Poll<Result<usize>>;
}

self 是實現該特征的類型的可變引用,使用 Pin 確保在異步操作過程中對象的內存位置不會改變
cx 是任務上下文,包含了任務的喚醒器等信息,用于在數據準備好時喚醒任務
buf 用于存儲讀取數據的緩沖區
Poll 枚舉表示操作的結果,可能是 Poll::Ready 表示操作已完成,返回實際讀取的字節數;也可能是 Poll::Pending 表示操作還未完成,需要等待。

&[u8] 實現 AsyncRead 的原因

靈活性和通用性:字節數組切片 &[u8] 是一種非常常見且靈活的數據表示方式,它可以表示內存中的一段連續字節數據。實現 AsyncRead 特征后,&[u8] 可以作為異步讀取操作的數據源,使得很多使用 AsyncRead 的代碼可以直接處理字節數組,無需額外的轉換。
例如,在測試代碼中,可以使用字節數組模擬文件或網絡數據進行異步讀取測試。
異步編程的一致性:在異步編程中,希望不同的數據源(如文件、網絡套接字、內存緩沖區等)都能以統一的方式進行異步讀取操作。通過讓 &[u8] 實現 AsyncRead 特征,保持了異步讀取操作的一致性,使得代碼更加簡潔和易于維護。

示例代碼

use tokio::io::{self, AsyncReadExt};#[tokio::main]
async fn main() -> io::Result<()> {// 定義一個字節數組,存儲了字符串 "Hello, World!" 的 UTF - 8 編碼字節數據let data = b"Hello, World!";let mut buffer = [0; 5];// 創建一個字節數組切片 &[u8],作為異步讀取的數據源let mut reader = &data[..];// 調用 read 方法(該方法是基于 AsyncRead 特征實現的),異步地從字節數組切片中讀取數據到 buffer 中// await 關鍵字用于等待讀取操作完成,最終返回實際讀取的字節數let n = reader.read(&mut buffer).await?;println!("Read {} bytes: {:?}", n, &buffer[..n]);Ok(())
}

實際應用場景

測試:在編寫異步 I/O 代碼的單元測試時,可以使用字節數組模擬不同的數據源,方便進行測試。例如,測試一個異步數據解析器時,可以使用字節數組提供測試數據。
內存數據處理:當需要對內存中的字節數據進行異步處理時,如對加密數據、壓縮數據等進行異步解密或解壓縮操作,可以直接使用字節數組作為數據源。

字節數組切片 &[u8] 實現 AsyncRead 特征,為異步編程提供了更多的靈活性和一致性,使得字節數組可以方便地作為異步讀取操作的數據源。

AsyncWriteExt

use tokio::io::{self, AsyncWriteExt};
use tokio::fs::File;#[tokio::main]
async fn main() -> io::Result<()> {let mut file = File::create("foo.txt").await?;// 將一個 &str 字符串轉變成一個字節數組:&[u8;10]// 然后 write 方法又會將這個 &[u8;10] 的數組類型隱式強轉為數組切片: &[u8]let n = file.write(b"some bytes").await?;println!("Wrote the first {} bytes of 'some bytes'.", n);Ok(())
}

AsyncWriteExt::write_all 將緩沖區的內容全部寫入到寫入器中

use tokio::io::{self, AsyncWriteExt};
use tokio::fs::File;#[tokio::main]
async fn main() -> io::Result<()> {let mut file = File::create("foo.txt").await?;file.write_all(b"some bytes").await?;Ok(())
}

tokio::io處理標準的輸入/輸出/錯誤

use tokio::fs::File;
use tokio::io;#[tokio::main]
async fn main() -> io::Result<()> {// &[u8] 是字節數組切片let mut reader: &[u8] = b"hello";let mut file = File::create("foo.txt").await?;// 異步的將讀取器( reader )中的內容拷貝到寫入器( writer )中// 字節數組 &[u8] 實現了 AsyncRead,所以這里可直接使用 readerio::copy(&mut reader, &mut file).await?;Ok(())
}

tokio::io分離讀寫器

錯誤寫法

io::copy(&mut socket, &mut socket).await

讀取器和寫入器都是同一個 socket,因此需要對其進行兩次可變借用,這明顯違背了 Rust 的借用規則

用同一個 socket 是不行的,為了實現目標功能,必須將 socket 分離成一個讀取器和寫入器
任何一個讀寫器( reader + writer )都可以使用 io::split 方法進行分離,最終返回一個讀取器和寫入器,兩者可以獨自使用,例如可以放入不同的任務中。

回聲服務端

use tokio::io;
use tokio::net::TcpListener;#[tokio::main]
async fn main() -> io::Result<()> {let listener = TcpListener::bind("127.0.0.1:6142").await?;loop {let (mut socket, _) = listener.accept().await?;tokio::spawn(async move {// split 操作和 io::copy 調用是在同一個異步任務上下文中執行的// 由于它們處于同一個任務中,所以不存在不同任務之間的數據傳遞開銷和同步問題// 任務的執行是連貫的,避免了因為跨任務操作而引入的額外復雜性和性能損耗let (mut rd, mut wr) = socket.split();if io::copy(&mut rd, &mut wr).await.is_err() {eprintln!("failed to copy");}});}
}

回聲客戶端

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;#[tokio::main]
async fn main() -> io::Result<()> {let socket = TcpStream::connect("127.0.0.1:6142").await?;let (mut rd, mut wr) = io::split(socket);// 創建異步任務,在后臺寫入數據tokio::spawn(async move {wr.write_all(b"hello\r\n").await?;wr.write_all(b"world\r\n").await?;// 有時,我們需要給予 Rust 一些類型暗示,它才能正確的推導出類型Ok::<_, io::Error>(())});let mut buf = vec![0; 128];loop {let n = rd.read(&mut buf).await?;if n == 0 {break;}println!("GOT {:?}", &buf[..n]);}Ok(())
}

tokio::spawn 是 Tokio 運行時提供的一個函數,用于創建一個新的異步任務并將其放入任務隊列中等待執行。這個新任務會在 Tokio 運行時的調度下異步執行,與當前代碼所在的任務是并發關系,而不是順序執行關系。
當執行到 tokio::spawn 時,它會立即將傳入的異步閉包包裝成一個新的異步任務并放入 Tokio 運行時的任務隊列中,然后代碼會繼續往下執行,不會等待這個新任務開始執行。
因此,let mut buf = vec![0; 128]; 這行代碼會緊接著 tokio::spawn 之后執行,而 tokio::spawn 內部的異步任務會在 Tokio 運行時調度到它時才開始執行。
Tokio 運行時的調度是基于事件驅動和任務優先級的,它會根據系統資源和任務的狀態動態地決定哪個任務先執行。所以,tokio::spawn 內部的任務可能在 let mut buf = vec![0; 128]; 之前執行,也可能在之后執行,甚至可能與后續代碼并發執行
假設 tokio::spawn 內部的任務執行需要一些時間(例如網絡延遲),而創建 buf 向量的操作很快,那么很可能 let mut buf = vec![0; 128]; 會先執行,然后才輪到 tokio::spawn 內部的任務開始執行

C-S修正

cargo run --bin server.rs
cargo run --bin client.rs

上述代碼跑起來之后,服務端不退出的話,客戶端會一直卡住,客戶端加如下函數即可解決

wr.shutdown().await ? ;

手動實現io

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;#[tokio::main]
async fn main() -> io::Result<()> {let listener = TcpListener::bind("127.0.0.1:6142").await?;loop {let (mut socket, _) = listener.accept().await?;tokio::spawn(async move {// 此處的緩沖區是一個 Vec 動態數組,它的數據是存儲在堆上,而不是棧上// 若改成 let mut buf = [0; 1024];,則存儲在棧上// 一個數據如果想在 .await 調用過程中存在,那它必須存儲在當前任務內// buf 會在 .await 調用過程中被使用,因此它必須要存儲在任務內let mut buf = vec![0; 1024];loop {match socket.read(&mut buf).await {// 返回值 `Ok(0)` 說明對端已經關閉Ok(0) => return,Ok(n) => {// Copy the data back to socket// 將數據拷貝回 socket 中if socket.write_all(&buf[..n]).await.is_err() {// 非預期錯誤,由于我們這里無需再做什么,因此直接停止處理return;}}Err(_) => {// 非預期錯誤,由于我們無需再做什么,因此直接停止處理return;}}}});}
}

若該緩沖區數組創建在棧上,那每條連接所對應的任務的內部數據結構看上去可能如下所示

struct Task {task: enum {AwaitingRead {socket: TcpStream,buf: [BufferType],},AwaitingWriteAll {socket: TcpStream,buf: [BufferType],}}
}

棧數組要被使用,就必須存儲在相應的結構體內,其中兩個結構體分別持有了不同的棧數組 [BufferType]
這種方式會導致任務結構變得很大
一般選擇緩沖區長度往往會使用分頁長度(page size),因此使用棧數組會導致任務的內存大小變得很奇怪甚至糟糕:
$page-size + 一些額外的字節

編譯器會進一步優化 async 語句塊的布局,而不是像上面一樣簡單的使用 enum
在實踐中,變量也不會在枚舉成員間移動。但是再怎么優化,任務的結構體至少也會跟其中的棧數組一樣大
因此通常情況下,使用堆上的緩沖區會高效實用的多

當任務因為調度在線程間移動時,存儲在棧上的數據需要進行保存和恢復,過大的棧上變量會帶來不小的數據拷貝開銷
因此,存儲大量數據的變量最好放到堆上

處理 EOF

當 TCP 連接的讀取端關閉后,再調用 read 方法會返回 Ok(0)。此時,再繼續下去已經沒有意義,因此需要退出循環。
忘記在 EOF 時退出讀取循環,是網絡編程中一個常見的 bug :

loop {match socket.read(&mut buf).await {Ok(0) => return,// ... 其余錯誤處理}
}

一旦讀取端關閉后,那后面的 read 調用就會立即返回 Ok(0),而不會阻塞等待,因此這種無阻塞循環會最終導致 CPU 立刻跑到 100%,并將一直持續下去,直到程序關閉。

小節

實際上,io::split 可以用于任何同時實現了 AsyncRead 和 AsyncWrite 的值,它的內部使用了 Arc 和 Mutex 來實現相應的功能。如果大家覺得這種實現有些重,可以使用 Tokio 提供的 TcpStream,它提供了兩種方式進行分離:

TcpStream::split 會獲取字節流的引用,然后將其分離成一個讀取器和寫入器。但由于使用了引用的方式,它們倆必須和 split 在同一個任務中。 優點就是,這種實現沒有性能開銷,因為無需 Arc 和 Mutex。
TcpStream::into_split 還提供了一種分離實現,分離出來的結果可以在任務間移動,內部是通過 Arc 實現

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72178.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72178.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72178.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

騰訊2025年軟件測試面試題

以下是基于騰訊等一線互聯網公司軟件測試崗位的面試趨勢和技術要求,025年出現的軟件測試面試題。這些問題涵蓋了基礎知識、自動化測試、性能測試、安全測試、編程能力等多個方面,供參考和準備。 一、基礎知識 軟件測試的基本概念

數據結構(陳越,何欽銘) 第四講 樹(中)

4.1 二叉搜索樹 4.1.1 二叉搜索樹及查找 Position Find(ElementTyoe X,BinTree BST){if(!BST){return NULL;}if(X>BST->Data){return Find(X,BST->Right)}else if(X<BST->Data){return Find(X,BST->Left)}else{return BST;} } Position IterFind(ElementTyp…

GEE學習筆記 28:基于Google Earth Engine的Landsat8纓帽變換土壤指數反演——亮度、綠度與濕度分量的提取

1.纓帽變換介紹 纓帽變換(Tasseled Cap Transformation,TCT),也稱為纓帽特征空間或纓帽系數,是一種用于遙感圖像分析的線性變換方法。它最初由美國農業部的研究人員E. Kauth和G. Thomas在1976年提出,用于增強陸地衛星(Landsat)圖像中的特定地表特征,如植被、土壤和城市…

【現代Web布局與動畫技術:卡片組件實戰分享】

&#x1f4f1; 現代Web布局與動畫技術&#xff1a;卡片組件實戰分享 &#x1f680; 引言 &#x1f31f; 在過去的開發過程中&#xff0c;我們共同實現了一個功能豐富的卡片組件&#xff0c;它不僅美觀&#xff0c;還具有交互性和響應式設計。這篇文章將分享這個組件背后的技術…

學習路之PHP --TP6異步執行功能 (無需安裝任何框架)

學習路之PHP --異步執行功能 &#xff08;無需安裝任何框架&#xff09; 簡介一、工具類二、調用三、異步任務的操作四、效果&#xff1a; 簡介 執行異步任務是一種很常見的需求&#xff0c;如批量發郵箱&#xff0c;短信等等執行耗時任務時&#xff0c;需要程序異步執行&…

STM32之影子寄存器

預分頻寄存器計數到一半的時候&#xff0c;改變預分頻值&#xff0c;此時不會立即生效&#xff0c;會等到計數完成&#xff0c;再從影子寄存器即預分頻緩沖器里裝載修改的預分頻值。 如上圖&#xff0c;第一行是內部時鐘72M&#xff0c;第二行是時鐘使能&#xff0c;高電平啟動…

Deepseek API接入IDE【VSCode Cline Cursor ChatBox Deepseek deepseek-reasoner】

本文解決以下疑難雜癥: 使用deepseek的最新接模型接入ide 使用deepseek的最新接模型接入vscode 使用deepseek的最新接模型接入vscode中的Cline 使用deepseek的最新接模型接入Cline 使用deepseek的最新接模型接入ChatBox 使用cursor接入Deepseek官方的的deepseek-reasoner…

微信小程序讀取寫入NFC文本,以及NFC直接啟動小程序指定頁面

一、微信小程序讀取NFC文本(yyy優譯小程序實現),網上有很多通過wx.getNFCAdapter方法來監聽讀取NFC卡信息,但怎么處理讀取的message文本比較難找,現用下面方法來實現,同時還解決幾個問題,1、在回調方法中this.setData不更新信息,因為this的指向問題,2、在退出頁面時,…

在Linux桌面上創建Idea啟動快捷方式

1、在桌面新建idea.desktop vim idea.desktop [Desktop Entry] EncodingUTF-8 NameIntelliJ IDEA CommentIntelliJ IDEA Exec/home/software/idea-2021/bin/idea.sh Icon/home/software/idea-2021/bin/idea.svg Terminalfalse TypeApplication CategoriesApplication;Developm…

VUE2生命周期頁面加載順序

使用 Vue CLI 4.5 運行 vue create myvue 創建項目&#xff0c;并通過 npm run serve 運行后&#xff0c;會生成一個標準的 Vue 項目目錄結構。以下是生成目錄的詳細說明&#xff0c;以及運行 localhost:8080 后 Vue 頁面的加載順序。 1. 生成目錄結構 運行 vue create myvue …

SV基礎(一):System Verilog與Verilog核心區別詳解

文章目錄 **1. 設計增強功能****數據類型擴展****接口(Interface)****2. 驗證功能增強****斷言(Assertions)****約束隨機測試****功能覆蓋率****3. 面向對象編程(OOP)****4. 測試平臺(Testbench)改進****5. 語法簡化****6. 其他關鍵區別****學習建議**System Verilog 是…

如何用 Python 進行機器學習

文章目錄 前言1. 環境準備Python安裝選擇Python開發環境安裝必要庫 2. 數據收集與加載3. 數據探索與可視化4. 數據預處理5. 模型選擇與訓練6. 模型評估7. 模型調優8. 模型部署 前言 使用 Python 進行機器學習一般可以按照以下步驟進行&#xff0c;下面將詳細介紹每個步驟及對應…

2021-05-27 C++找出矩陣數組中值最大的元素和它在數組中的位置

緣由各位大佬&#xff0c;這個應該怎么做_編程語言-CSDN問答 void 找出數組中值最大的元素和它在數組中的位置() {//緣由https://ask.csdn.net/questions/7436585?spm1005.2025.3001.5141int a[4][4], aa 0, aaa 0, d 0, x 0;while (aa < 4 && aaa < 4)std…

在 IntelliJ IDEA 中啟動多個注冊到 Nacos 的服務

使用場景&#xff1a;邊改代碼&#xff0c;邊和前端聯調。 在微服務架構中&#xff0c;服務注冊與發現是核心功能之一。Nacos 作為一款流行的開源服務注冊與配置管理工具&#xff0c;被廣泛應用于微服務架構中。本文將介紹如何在 IntelliJ IDEA 中配置并啟動多個注冊到 Nacos …

DeepSeek開源周Day2:DeepEP - 專為 MoE 模型設計的超高效 GPU 通信庫

項目地址&#xff1a;https://github.com/deepseek-ai/DeepEP 開源日歷&#xff1a;2025-02-24起 每日9AM(北京時間)更新&#xff0c;持續五天 (2/5)&#xff01; ? ? 引言 在大模型訓練中&#xff0c;混合專家模型&#xff08;Mixture-of-Experts, MoE&#xff09;因其動…

HTTP學習——————(四)TLS等加密算法

前文學習&#xff1a; 一、二、三 學習來源網站 &#xff1a; 極客時間 TLS 目的&#xff1a;身份驗證、保密性、完整性 解決問題&#xff1a; Record記錄協議——對稱加密 Handshake握手協議———1.驗證通訊雙方身份 2.交換加解密安全套件 3.協商加密參數 有密鑰交換算法…

FastExcel vs EasyExcel vs Apache POI:三者的全面對比分析

一、核心定位與歷史沿革 Apache POI&#xff08;1990s-&#xff09; 作為Java生態中最古老的Excel處理庫&#xff0c;提供對.xls/.xlsx文件的全功能支持。其核心價值在于對Excel規范的完整實現&#xff0c;包括單元格樣式、公式計算、圖表操作等深度功能。但存在內存消耗大&…

辛格迪客戶案例 | 鼎康生物電子合約系統(eSign)項目

01 案例企業 鼎康(武漢)生物醫藥有限公司于2013年06月19日成立 &#xff0c;是一家總部位于湖北武漢的CDMO公司&#xff0c;堅持以客戶為中心&#xff0c;以及時、經濟和高質量為服務導向。鼎康生物擁有先進的150,000平方英尺的生產廠房&#xff0c;生產設施位于中國武漢的Bio…

multer 依賴詳解

multer 是一個用于處理 multipart/form-data 類型表單數據的 Node.js 中間件&#xff0c;主要用于文件上傳。它基于 busboy 構建&#xff0c;使用起來非常方便。 一、安裝 npm install multer 二、基本使用 const express require("express");const multer req…

點云配準技術的演進與前沿探索:從傳統算法到深度學習融合(4)

4、點云配準面臨的挑戰與應對策略 4.1 點云配準面臨的主要挑戰 在點云配準的實際應用中&#xff0c;盡管已經取得了顯著的研究成果&#xff0c;但仍然面臨著諸多復雜而嚴峻的挑戰&#xff0c;這些挑戰嚴重制約了點云配準技術在更多領域的廣泛應用和深入發展。 在自動駕駛場景…