初次体验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.mzph.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; 据清华大学网络空间测绘联合研…