初次体验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.rhkb.cn/news/31734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DBeaver安装教程+连接TDengine数据库

为TDengine安装的DBeaver教程 安装 23.1.1 版本以上的DBeaver 因为官方文档说这个版本之上的DBeaver才支持TDengine内嵌前往DBeaver 官方文档进行版本下载滑到链接最下面点击进入 点击download&#xff0c;进入选择下载版本 等待下载成功即可双击自行安装 打开数据库连接TDen…

Java 学习记录:基础到进阶之路(一)

今天&#xff0c;让我们深入到 Java 项目构建、基础语法及核心编程概念的领域&#xff0c;一探究竟。 软件安装及环境配置请查看之前更新的博客有着详细的介绍&#xff1a; IDEA软件安装&环境配置&中文插件-CSDN博客 目录 1.Java 项目构建基础 1.项目中的 SRC 目录…

【蓝桥杯】每天一题,理解逻辑(3/90)【Leetcode 快乐数】

闲话系列&#xff1a;每日一题&#xff0c;秃头有我&#xff0c;Hello&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;,我是IF‘Maxue&#xff0c;欢迎大佬们来参观我写的蓝桥杯系列&#xff0c;我好久没有更新博客了&#xff0c;因为up猪我寒假用自己的劳动换了…

STM32Cubemx-H7-7-OLED屏幕

如何把江科大的OLED标准库文件换成hal库的文件 前言 本文讲解如在hHAL库中使用OLED&#xff0c;其实江科大做的文件好已经很好了 只讲操作&#xff0c;不讲废话&#xff0c;默认大家都有32基本操作 创建工程 首先创建工程 把那两个引脚设置成开漏 获取标准库文件 打开江科大O…

基于 Vue 的Deepseek流式加载对话Demo

目录 引言组件概述核心组件与功能实现1. 消息显示组件&#xff08;Message.vue&#xff09;2. 输入组件&#xff08;Input.vue&#xff09;3. 流式请求处理&#xff08;useDeepseek.ts&#xff09;4. 语音处理模块&#xff08;Voice.vue&#xff09; 总结Demo Github 地址 引言…

Pixelmator Pro for Mac 专业图像处理软件【媲美PS的修图】

介绍 Pixelmator Pro&#xff0c;是一款非常强大、美观且易于使用的图像编辑器&#xff0c;专为 Mac 设计。采用单窗口界面、基于机器学习的智能图像编辑、自动水平检测&#xff0c;智能快速选择及更好的修复工具等功能优点。许多非破坏性的专业编辑工具可让您进行最佳的照片处…

YOLO结合bytetrack对车辆目标跟踪计数

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对车辆目标数据集进行训练和优化&#xff0c;该数据集包含丰富的车辆目标图像样本…

通义万相2.1 图生视频:为AI绘梦插上翅膀,开启ALGC算力领域新纪元

通义万相2.1图生视频大模型 通义万相2.1图生视频技术架构万相2.1的功能特点性能优势与其他工具的集成方案 蓝耘平台部署万相2.1核心目标典型应用场景未来发展方向 通义万相2.1ALGC实战应用操作说明功能测试 为什么选择蓝耘智算蓝耘智算平台的优势如何通过API调用万相2.1 写在最…

软考中级_【软件设计师】知识点之【知识产权】

简介 知识产权模块主要涉及软件行业相关法律保护体系&#xff0c;包括著作权、专利权、商标权及商业秘密等内容。重点涵盖软件著作权登记流程、源代码保护范围、专利创新性认定标准&#xff0c;以及开源协议&#xff08;如GPL、MIT&#xff09;的法律约束力。考生需掌握**《计算…

Kafka×DeepSeek:智能决策破取经八十一难!

《西游记》的故事中&#xff0c;唐僧师徒四人历经九九八十一难&#xff0c;从东土大唐前往西天取经。一路上&#xff0c;火焰山酷热难耐、通天河水位忽高忽低、妖怪神出鬼没…… 现在&#xff0c;唐僧师徒取经路上的种种难题&#xff0c;在KafkaDeepSeek双引擎加持下有了全新解…

nextjs15使用next-intl实现国际化多语言

在nextjs15当中使用next-intl可以轻松实现国际化&#xff0c;本文将着重阐述&#xff0c;如何在nextjs15使用next-intl。 一、创建项目安装依赖 1、创建nextjs项目 pnpm dlx create-next-app my-app 2、安装next-intl pnpm add next-intl 二、创建组件文件 1、项目结构 …

【C++模板】:开启泛型编程之门(函数模版,类模板)

&#x1f4dd;前言&#xff1a; 在上一篇文章C内存管理中我们介绍了C的内存管理&#xff0c;重点介绍了与C语言的区别&#xff0c;以及new和delete。这篇文章我们将介绍C的利器——模板。 在C编程世界里&#xff0c;模板是一项强大的特性&#xff0c;它为泛型编程奠定了坚实基础…

Android : Camera之CHI API

来自&#xff1a; https://www.cnblogs.com/szsky/articles/10861918.html 一、CAM CHI API功能介绍&#xff1a; CHI API建立在Google HAL3的灵活性基础之上&#xff0c;目的是将Camera2/HAL3接口分离出来用于使用相机功能&#xff0c;它是一个灵活的图像处理驱动程序&#…

项目部署到生产上遇到的网络问题

今天项目上线不顺利,原因就是网络能 telnet 通过&#xff0c;但是就是访问不到接口。 项目使用的是 docker 部署的方式。一开始以为是网络权限没开通&#xff0c;一直找运维部门帮忙看&#xff0c;也都没发现问题&#xff0c;网络部门已经把权限都开了。 折腾了一番后&#x…

Odoo 18 中的列表视图装饰属性

引言 列表视图装饰在 Odoo 中提供了一种基于特定条件在列表/树形视图中直观突出显示记录或字段的方式。这些装饰能够提升用户体验&#xff0c;使用户更轻松地识别重要记录。在 Odoo 18 中&#xff0c;有多个属性可用于列表视图装饰&#xff0c;为数据管理提供了灵活性。 以下…

SpringMVC中有关请求参数的问题(映射路径,传递不同的参数)

目录 请求映射路径 get请求与psot请求发送普通参数 get请求发送参数 post请求发送参数 post请求乱码问题 5种参数类型传递 普通参数传递&#xff08;不同名&#xff09; 实体类对象传递 数组传递 集合参数 json数据传递参数 JSON数组 JSON对象 ​编辑 JSON引用集…

图片查看器:用PyQt5实现本地图片预览工具

通过python代码&#xff0c;基于PyQt5实现本地图片预览查看工具。 我们对窗口进行了圆角设计&#xff0c;图片的翻页按钮半透明处理&#xff0c;当鼠标移动至按钮上的动画效果&#xff0c;当选择某一张图片&#xff0c;进行左右翻页则轮播同目录所有支持的图片格式。 import …

算法优选系列(1.双指针_下)

目录 五. 有效三角形的个数&#xff08;medium&#xff09; 题目链接&#xff1a;有效三角形的个数 解法: 代码&#xff1a; 六&#xff1a;和为 s 的两个数字&#xff08;easy&#xff09; 题目链接&#xff1a;和为 s 的两个数字 解法&#xff1a; 代码; 七&#xf…

【数据结构】2算法及分析

0 章节 &#xff11;&#xff0e;&#xff14;到1&#xff0e;&#xff15;小节。 掌握算法概念、特性、描述、算法性能时间复杂度和空间复杂度&#xff1b; 理解递归含义&#xff1f; 掌握实现递归的条件和时机&#xff1b; 应用简单递归问题的算法设计&#xff1b; 重点 算法…

要在Unreal Engine 5(UE5)中实现角色打击怪物并让怪物做出受击反应,

UE5系列文章目录 文章目录 UE5系列文章目录前言一、实现思路二、最终效果 前言 ue5角色受击没有播放受击动画&#xff0c;主角达到怪物身上没有反应 一、实现思路 要在Unreal Engine 5&#xff08;UE5&#xff09;中实现角色打击怪物并让怪物做出受击反应&#xff0c;你需要…