初次體驗Tauri和Sycamore(3)通道實現

?
原創作者:莊曉立(LIIGO)
原創時間:2025年03月10日(發布時間)
原創鏈接:https://blog.csdn.net/liigo/article/details/146159327
版權所有,轉載請注明出處。

tauri-splash


20250310 LIIGO備注:本文源自系列文章第1篇《初次體驗Tauri和Sycamore (1)》,從中抽取出來獨立成文(但并無更新和修訂),專注于探究Tauri通道的底層實現(實際上也沒有足夠底層)。理由:1.原文已經很長,需要精簡;2.原文主體是初級技術內容,僅這一節相對深入,顯得格格不入。(如無意外,這將是本系列文章的終結。)


20241118 LIIGO補記:出于好奇,簡單研究一下Tauri通道的底層實現。

在JS層,創建Channel對象生成通道ID,并關聯onmessage處理函數;在傳輸層,通過invoke()調用后端Command,傳入Channel對象作為參數(實質上是傳入通道ID);在Rust層,根據通道ID構造后端Channel對象,向客戶端指定的Channel發送Message。如何向通道發送Message是后續關注的重點。


JS層創建Channel的源碼如下:

class Channel<T = unknown> {id: number// @ts-expect-error field used by the IPC serializerprivate readonly __TAURI_CHANNEL_MARKER__ = true#onmessage: (response: T) => void = () => {// no-op}#nextMessageId = 0#pendingMessages: Record<string, T> = {}constructor() {this.id = transformCallback(({ message, id }: { message: T; id: number }) => {// the id is used as a mechanism to preserve message orderif (id === this.#nextMessageId) {this.#nextMessageId = id + 1this.#onmessage(message) // 前端用戶收到此message// process pending messages// ...} else {this.#pendingMessages[id.toString()] = message}});}// ...
}function transformCallback<T = unknown>(callback?: (response: T) => void, once = false): number {return window.__TAURI_INTERNALS__.transformCallback(callback, once)
}

JS層Channel構造函數內部,調用transformCallback為一個回調函數生成唯一ID(它基于Crypto.getRandomValues()的實現能保證ID唯一嗎我存疑),并將二者關聯至window對象:window['_回調ID'] = ({message, id})=>{ /*...*/};。此處生成的ID也稱為通道ID,將被invoke函數傳遞給Rust層(參見上文前端調用Command)。后端數據通過通道到達前端后,可通過通道ID反查并調用該回調函數接收后端數據。注意區分通道ID、消息ID和后文的數據ID。


Rust層通過JavaScriptChannelId::channel_onChannel::new_with_id構造Channel對象實例。

impl JavaScriptChannelId {/// Gets a [`Channel`] for this channel ID on the given [`Webview`].pub fn channel_on<R: Runtime, TSend>(&self, webview: Webview<R>) -> Channel<TSend> {let callback_id = self.0;let counter = AtomicUsize::new(0);Channel::new_with_id(callback_id.0, move |body| {let i = counter.fetch_add(1, Ordering::Relaxed);if let Some(interceptor) = &webview.manager.channel_interceptor {if interceptor(&webview, callback_id, i, &body) {return Ok(());}}let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);webview.state::<ChannelDataIpcQueue>().0.lock().unwrap().insert(data_id, body);webview.eval(&format!("window.__TAURI_INTERNALS__.invoke('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then((response) => window['_' + {}]({{ message: response, id: {i} }})).catch(console.error)",callback_id.0))?;Ok(())})}
}

Channel::new_with_id有兩個參數,一個是通道ID(或稱callback_id),一個是向前端發送數據的on_message函數。這個on_message的命名有誤導性,讓人以為是接收函數,但看Channel::send()函數源碼可以確認on_message是發送函數。

impl<TSend> Channel<TSend> {fn new_with_id<F: Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync + 'static>(id: u32,on_message: F,) -> Self {// ...}/// Sends the given data through the channel.pub fn send(&self, data: TSend) -> crate::Result<()> where TSend: IpcResponse, {(self.on_message)(data.body()?)}
}

Rust層Channel發送數據的實現代碼就在上面JavaScriptChannelId::channel_on(webview)函數內部,即new_with_id()的on_message參數閉包函數內,它主要干了如下幾件事:

  • 生成數據ID(data_id):let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);
  • 將要數據存入發送緩存隊列并關聯data_id:webview.state::<ChannelDataIpcQueue>()...insert(data_id, body)
  • 生成JS代碼并提交給前端執行(分兩步):webview.eval(JSCODE)
    • fetch: invoke('plugin:__TAURI_CHANNEL__|fetch', null, ...data_id...)
    • callback: window['_通道ID']({ message: response, id: {i} }) (調用JS端回調函數, {i}為此通道內消息ID,即序號)

再看一下fetch源碼(上文invoke('plugin:__TAURI_CHANNEL__|fetch', ...)將調用此后端Command):

#[command(root = "crate")]
fn fetch(request: Request<'_>,cache: State<'_, ChannelDataIpcQueue>,
) -> Result<Response, &'static str> {if let Some(id) = request.headers().get(CHANNEL_ID_HEADER_NAME).and_then(|v| v.to_str().ok()).and_then(|id| id.parse().ok()){if let Some(data) = cache.0.lock().unwrap().remove(&id) {Ok(Response::new(data))} else {Err("data not found")}} else {Err("missing channel id header")}
}

fetch命令的作用是從發送緩存隊列中取出與參數data_id關聯的數據返回給前端,同時從發送緩存隊列中移除。fetch執行后,通過通道發送的數據就從后端到了前端。注意時序,是后端主動提交JS代碼讓前端執行,前端才被動發起fetch調用,Tauri正是通過這種方式實現后端向前端“推送”數據。數據被推送至前端后,可能還要經歷緩存階段才提交給Channel用戶,確保用戶有序接收。


調用鏈:(JS層)創建Channel,發起調用后端某Command(傳入通道ID),(Rust層)把通道ID反序列化為Channel,將待發送數據緩存,調度前端執行JS代碼(webview.eval()),(JS層)通過fetchCommand拉取后端緩存數據,處理亂序數據接收,執行用戶層onmessage回調,完成單次數據傳輸。

我原來猜測通道Channel是Command之外另一種更高效的數據傳輸方案,但事實證明我錯了。通過上述源碼分析可知,Channel實際上是基于Command實現的更高層的邏輯抽象。Tauri通道發送數據,本質上還是調用Command,只是經過封裝之后更適合“后端推送流式數據”應用場景。相比使用普通無通道Command傳輸數據,其區別在于工作模式:無通道傳輸,是前端單次主動拉取;有通道傳輸,是后端多次主動推送,且保證有序送達。

?

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

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

相關文章

代碼隨想錄|二叉樹|07二叉樹周末總結

對前面01~06二叉樹內容進行小結&#xff0c;直接看下面的總結文檔&#xff1a; 本周小結&#xff01;&#xff08;二叉樹&#xff09; | 代碼隨想錄

藍耘賦能通義萬相 2.1:用 C++ 構建高效 AI 視頻生成生態

目錄 開篇&#xff1a;AI 視頻生成新時代的號角 通義萬相 2.1&#xff1a;AI 視頻生成的領軍者 核心技術揭秘 功能特點展示 與其他模型的全面對比 C&#xff1a;高效編程的基石 C 的發展歷程與特性 C 在 AI 領域的廣泛應用 通義萬相 2.1 與 C 的完美融合 融合的意義與…

【一句話經驗】ubuntu vi/vim 模式自動設置為paste

從centos過來&#xff0c;發現ubutun有些地方不習慣&#xff0c;尤其是vi的粘貼&#xff0c;默認自動進去了代碼模式&#xff0c;導致每次粘貼必須得set paste&#xff0c;否則會出現問題。 解決辦法非常簡單&#xff0c;按照下面命令執行即可&#xff1a; cd ~ echo "…

自然語言處理文本分析:從詞袋模型到認知智能的進化之旅

清晨&#xff0c;當智能音箱準確識別出"播放周杰倫最新專輯"的模糊語音指令時&#xff1b;午間&#xff0c;企業輿情系統自動標記出十萬條評論中的負面情緒&#xff1b;深夜&#xff0c;科研人員用GPT-4解析百萬篇論文發現新材料線索——這些場景背后&#xff0c;是自…

《Python基礎教程》附錄B筆記:Python參考手冊

《Python基礎教程》第1章筆記&#x1f449;https://blog.csdn.net/holeer/article/details/143052930 附錄B Python參考手冊 Python標準文檔是完整的參考手冊。本附錄只是一個便利的速查表&#xff0c;當你開始使用Python進行編程后&#xff0c;它可幫助你喚醒記憶。 B.1 表…

uniapp+Vue3 組件之間的傳值方法

一、父子傳值&#xff08;props / $emit 、ref / $refs&#xff09; 1、props / $emit 父組件通過 props 向子組件傳遞數據&#xff0c;子組件通過 $emit 觸發事件向父組件傳遞數據。 父組件&#xff1a; // 父組件中<template><view class"container">…

【MySQL篇】MySQL基本查詢詳解

目錄 前言&#xff1a; 1&#xff0c;Create 1.1&#xff0c;單行數據全列插入 1.2&#xff0c;單行數據指定列插入 1.3&#xff0c;多行數據全列插入 1.4&#xff0c;多行數據指定列插入 1.5&#xff0c;插入否則更新 1.6&#xff0c;替換 2&#xff0c;Retrieve …

【Python入門】一篇掌握Python中的字典(創建、訪問、修改、字典方法)【詳細版】

&#x1f308; 個人主頁&#xff1a;十二月的貓-CSDN博客 &#x1f525; 系列專欄&#xff1a; &#x1f3c0;《Python/PyTorch極簡課》_十二月的貓的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻擋不了春天的腳步&#xff0c;十二點的黑夜遮蔽不住黎明的曙光 目…

每日一題——兩數相加

兩數相加 問題描述問題分析解題思路代碼實現代碼解析注意事項示例運行總結 問題描述 給定兩個非空鏈表&#xff0c;表示兩個非負整數。鏈表中的每個節點存儲一個數字&#xff0c;數字的存儲順序為逆序&#xff08;即個位在鏈表頭部&#xff09;。要求將這兩個數字相加&#xff…

制作自定義鏡像

1. 確定軟件包 確定自己的環境都需要哪些命令&#xff0c;然后&#xff0c;從鏡像文件或者yum源下載響應的安裝包。 bash基本是必選的 &#xff08;bash-5.1.8-10.oe2203sp2.aarch64.rpm&#xff09; vim也是有必要的 &#xff08;vim-enhanced-9.0-15.oe2203sp2.aarch64.rpm…

WHAT - 前端性能指標

目錄 核心 Web Vitals&#xff08;Core Web Vitals&#xff09;加載性能指標網絡相關指標交互和響應性能指標內存與效率指標推薦的監控工具優化策略與建議推薦學習路線 作為前端開發者&#xff0c;理解并掌握關鍵的性能指標對優化 Web 應用至關重要。 以下是前端性能優化中常見…

C++20 模塊:告別頭文件,迎接現代化的模塊系統

文章目錄 引言一、C20模塊簡介1.1 傳統頭文件的局限性1.2 模塊的出現 二、模塊的基本概念2.1 模塊聲明2.2 模塊接口單元2.3 模塊實現單元 三、模塊的優勢3.1 編譯時間大幅減少3.2 更好的依賴管理3.3 命名空間隔離 四、如何使用C20模塊4.1 編譯器支持4.2 示例項目4.3 編譯和運行…

Apache Hudi 性能測試報告

一、測試背景 數據湖作為一個集中化的數據存儲倉庫,支持結構化、半結構化以及非結構化等多種數據格式,數據來源包含數據庫數據、增量數據、日志數據以及數倉上的存量數據等。數據湖能夠將這些不同來源、不同格式的數據集中存儲和管理在高性價比的分布式存儲系統中,對外提供…

sql靶場5-6關(報錯注入)保姆級教程

目錄 sql靶場5-6關&#xff08;報錯注入&#xff09;保姆級教程 1.第五關 1.步驟一&#xff08;閉合&#xff09; 2.步驟二&#xff08;列數&#xff09; 3.報錯注入深解 4.報錯注入格式 5.步驟三&#xff08;數據庫表名&#xff09; 6.常用函數 7.步驟四&#xff08;表…

OSPF-單區域的配置

一、單區域概念&#xff1a; 單區域OSPF中&#xff0c;整個網絡被視為一個區域&#xff0c;區域ID通常為0&#xff08;骨干區域&#xff09;。所有的路由器都在這個區域內交換鏈路狀態信息。 補充知識點&#xff1a; OSPF為何需要loopback接口&#xff1a; 1.Loopback接口的…

LeetCode100之二叉樹的直徑(543)--Java

1.問題描述 給你一棵二叉樹的根節點&#xff0c;返回該樹的 直徑 。 二叉樹的 直徑 是指樹中任意兩個節點之間最長路徑的 長度 。這條路徑可能經過也可能不經過根節點 root 。 兩節點之間路徑的 長度 由它們之間邊數表示。 示例1 輸入&#xff1a;root [1,2,3,4,5] 輸出&#…

C語言每日一練——day_4

引言 針對初學者&#xff0c;每日練習幾個題&#xff0c;快速上手C語言。第四天。&#xff08;連續更新中&#xff09; 采用在線OJ的形式 什么是在線OJ&#xff1f; 在線判題系統&#xff08;英語&#xff1a;Online Judge&#xff0c;縮寫OJ&#xff09;是一種在編程競賽中用…

工作流編排利器:Prefect 全流程解析

工作流編排利器&#xff1a;Prefect 全流程解析 本文系統講解了Prefect工作流編排工具&#xff0c;從基礎入門到高級應用&#xff0c;涵蓋任務與流程管理、數據處理、執行器配置、監控調試、性能優化及與其他工具集成等內容&#xff0c;文末項目實戰示例&#xff0c;幫助讀者全…

Web Workers 客戶端 + 服務端應用

一. Web Workers 客戶端應用 使用 JavaScript 創建 Web Worker 的步驟如下&#xff1a; 1.創建一個新的 JavaScript 文件&#xff0c;其中包含要在工作線程中運行的代碼&#xff08;耗時任務&#xff09;。該文件不應包含對 DOM 的引用&#xff0c;因為在工作線程中無法訪問 …

大模型工具Ollama存在安全風險

國家網絡安全通報中心&#xff1a;大模型工具Ollama存在安全風險 來源&#xff1a;國家網絡與信息安全信息通報中心 3月3日&#xff0c;國家網絡安全通報中心發布關于大模型工具Ollama存在安全風險的情況通報&#xff0c;內容如下&#xff1a; 據清華大學網絡空間測繪聯合研…