当前位置: 首页 > news >正文

解锁前端新潜能:如何使用 Rust 锈化前端工具链

前言

近年来,Rust的受欢迎程度不断上升。首先,在操作系统领域,Rust 已成为 Linux 内核官方认可的开发语言之一,Windows 也宣布将使用 Rust 来重写内核,并重写部分驱动程序。此外,国内手机厂商 Vivo 也宣布使用 Rust 开发了名为“蓝河”的操作系统。除此之外,Rust 在图形渲染、游戏开发、中间件开发、边缘计算、计算安全等领域也是遍地开花,可以说,Rust 正在以惊人的速度重塑着各个领域的发展,让人不禁感叹 Rust 已经在重写万物了。

微软

那回到前端领域,正在进行一场构建工具的革命,除了老牌的 Babel 竞争对手swc,一些新兴的前端构建工具也都在使用 Rust 进行开发,例如Turbopack、Parcel,对标 Webpack 的Rspack,对标 Vite 的Farm等等。所以,对于广大前端同胞来说,C/C++ 太难,学习和掌握 Rust 是一个不错的选择,虽然 Rust 也不见得容易许多,它有着陡峭的学习曲线,但它或许是我们突破闭塞的前端区间的一把钥匙,帮助我们打开通往新世界的大门。

锈化开发工具的方式

虽说 Rust 的学习曲线可能相对陡峭,但笔者认为这是对于要全面掌握 Rust 这门语言而言的,而我们学习语言的目的最重要的是掌握一项可以帮我们解决问题的技能,因此,对于 Rust 不需要抱有太多的恐惧和敬畏之心,只需要摒除杂念立马开始学习 + 撸码,剩下的就交给时间来慢慢积累经验。此外,对于不是那么复杂应用来说,熟悉 Rust 基本语法和数据结构,翻过「所有权机制」和「生命周期」两座大山,基本也足以应付了。

本文建立在读者已经有一定的 Rust 知识基础上,对于 Rust 基本语法就不做赘述了。当前,大部分前端研发都是在 Node 环境下进行的,所以我们通过 Rust 来改造开发工具,主要有两种形式:

  • 使用 WASM 的方式,基于wasm-pack,将 Rust 代码编译成 WASM,以供 Node 调用

  • 将 Rust 应用编译成 Node addons,通过 Node API 的方式供 Node 调用,可以基于napi-rs和neon来实现

在这两种方式的选择上,主要取决于你是否需要完整地访问 Node API,WASM 出于安全性的考虑,对于 Node 能力的调用存在限制,那么此时就应该选择 Node addons 的方式了。而napi-rs和neon的选择的话,napi-rs相对而言比较简单和轻量,而且针对不同版本的 Node 不需要重新编译,所以我们考虑选择napi-rs作为锈化开发工具的方式。

初识 NAPI-RS

我们可以通过 napi-rs 的开发工具 ****@napi-rs/cli**以及项目模板来初始化一个应用,这里推荐使用项目模板,因为经过笔者的测试,开发工具创建的项目内容上相较于模板比较落后,对于后续深入使用上会造成一定的困惑。

NAPI-RS 项目模板

从 napi-rs 项目模板内容上看,可以发现项目结构完善,工程化相关能力非常齐全,提供了构建工具、测试用例编写、Github CI 工作流等等必须的能力,我们只需要关注编码就可以了。

我们先来关注一下生成的 napi-rs 项目文件。从package.jsonnpm分析可以看出,一个 napi-rs 项目主要是由主包和 npm 下的针对不同平台的编译构建结果子包组成,napi-rs 会根据用户的配置,将用户的 Rust 代码构建为不同平台下的 Node 扩展 binding 文件,这些文件会放到 npm 下对应的平台目录中,再由 package.json 中 main 字段指定导出,用户在安装主包的时候,会根据用户电脑情况加载对应构建结果子包。

{"name": "@tarojs/parse-css-to-stylesheet-darwin-x64","version": "0.0.25","os": ["darwin"],"cpu": ["x64"],"main": "parse-css-to-stylesheet.darwin-x64.node","files": ["parse-css-to-stylesheet.darwin-x64.node"],"license": "MIT","engines": {"node": ">= 10"},"repository": "https://github.com/NervJS/parse-css-to-stylesheet"
}

而在主包入口index.js中将根据用户宿主平台,加载对应的扩展文件。

...
switch (platform) {case 'win32':switch (arch) {case 'x64':localFileExisted = existsSync(join(__dirname, 'parse-css-to-stylesheet.win32-x64-msvc.node'))try {if (localFileExisted) {nativeBinding = require('./parse-css-to-stylesheet.win32-x64-msvc.node')} else {nativeBinding = require('@tarojs/parse-css-to-stylesheet-win32-x64-msvc')}} catch (e) {loadError = e}break...}break...
}
...

@napi-rs/triples这个包中可以看到所有支持的平台列表,而对于常规性的 Node 应用来说,我们不需要构建这么多平台,一般来说构建x86_64-apple-darwinaarch64-apple-darwinx86_64-pc-windows-msvcx86_64-unknown-linux-gnu这四个平台也足够了,这样也能减少 CI 的构建时间。

napi-rs 默认构建的平台是x86_64-apple-darwinx86_64-pc-windows-msvcx86_64-unknown-linux-gnu,在这里可以看到,所以为了增加 MAC Book M 系列电脑的支持,我们需要增加aarch64-apple-darwin的配置,可以在 package.json 中napi字段中添加,配置如下:

"napi": {"binaryName": "taro","triples": {"default": true,"additional": ["aarch64-apple-darwin"]}
},

接下来就可以开始我们的编码之旅咯!

基于 NAPI-RS 开发 Node 扩展

基于 napi-rs 开发 Node 扩展,除了 Rust 编码本身外,无非需要关注两种情况,即 JavaScript 调用 Rust 和 Rust 调用 JavaScript。

JavaScript 调用 Rust

调用 Rust 函数

这是最常见的用法,因为我们一般使用 Rust 开发 Node 扩展,也是为了将一些 CPU 密集型任务的任务使用 Rust 来实现,再暴露给 JS 来调用,从而达到提升应用性能的目的,最为常见的是 Rust 暴露方法给到 JS 调用,通过项目模板生成的 napi-rs 示例也可以看到。

// src/lib.rs 
use napi_derive::napi;#[napi]
pub fn plus_100(input: u32) -> u32 {input + 100
}

如上代码,通过给plus_100函数添加#[napi]属性宏,这样可以标记该函数,表示该函数可以通过 N-API 在 Node.js 中调用,在项目编译后的 typing 文件中,我们能看到对应生成了 JS 函数.

export function plus100(input: number): number

可以看到这里生成 JS 函数名是 napi-rs 自己的规则,我们也可以自定义暴露的函数名,通过js_name属性可以指定。

#[napi(js_name = "plus_100")]
pub fn plus_100(input: u32) -> u32 {input + 100
}

当然,除了暴露函数这一基本操作之外,我们还可以暴露常量、对象、类、enum 等等给到 JS 侧去调用,这些可以通过 napi-rs 的官方文档可以查阅到。

以 Object 作为参数

而在 JS 调用 Rust 编码中,最需要关注的是调用函数时,JS 侧给 Rust 传对象作为参数,这里为了提升性能,建议提前在 Rust 中定义好传递对象的数据结构,在 JS 中以引入该数据结构定义,规范数据传递即可。

// 定义好数据结构
// napi(object) 表示紧随其后的 struct (结构体)将通过 N-API 以 JavaScript 对象的形式暴露出去
#[napi(object)]
pub struct Project {pub project_root: String,pub project_name: String,pub npm: NpmType,pub description: Option,pub typescript: Option,pub template: String,pub css: CSSType,pub auto_install: Option,pub framework: FrameworkType,pub template_root: String,pub version: String,pub date: Option,pub compiler: Option,pub period: PeriodType,
}

JS 中调用

// 函数定义,其中 Project 由 Rust binding 中暴露
export function createProject(conf: Project)// 函数调用
createProject({projectRoot: projectDir,projectName,template,npm,framework,css: this.conf.css || CSSType.None,autoInstall: autoInstall,templateRoot: getRootPath(),version: getPkgVersion(),typescript: this.conf.typescript,date: this.conf.date,description: this.conf.description,compiler: this.conf.compiler,period: PeriodType.CreateAPP,
})

Rust 调用 JavaScript

而 Rust 中也可以调用 JS 提供的方法,这在做 Node 开发工具的时候非常有用,因为有时候我们需要读取开发人员的配置代码给到 Rust 调用,其中就可能会遇到 Rust 调用 JavaScript 中函数的情况。

一个调用 JS 函数的简单例子

在 napi-rs 中调用 JS 函数主要通过ThreadsafeFunction来实现,请看例子:

#[napi]
pub fn call_threadsafe_function(callback: ThreadsafeFunction) -> Result<()> {for n in 0..100 {let tsfn = callback.clone();thread::spawn(move || {tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);});}Ok(())
}

在上述例子中,call_threadsafe_function函数接受了一个类型为ThreadsafeFunction<u32>的参数,这表明call_threadsafe_function被编译为 JS 函数后将接受一个回调函数作为参数,而该回调函数的有效参数为u32number类型,而在call_threadsafe_function函数体中,通过thread::spawn开辟子线程,以阻塞的方式调用这个传入的回调函数。

通过ThreadsafeFunctioncall方法可以调用到传入的 JS 回调函数,但是我们会发现它拿不到返回值,如果我们需要获取到 JS 回调函数的返回值时,我们需要使用call_with_return_valuecall_async两个方法。

获取 JS 函数的返回值

对比callcall_with_return_value的实现可以看出,call_with_return_valuecall多一个回调函数参数,并且可以指定 JS 回调函数返回值的类型,并且该类型需要满足FromNapiValue这个 trait,因为call_with_return_value在处理 JS 回调函数时会调用它的from_napi_value方法将 JS 数据转为 Rust 的数据类型。

// https://github.com/napi-rs/napi-rs/blob/main/crates/napi/src/threadsafe_function.rs#L428
pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) -> Status {self.handle.with_read_aborted(|aborted| {if aborted {return Status::Closing;}unsafe {sys::napi_call_threadsafe_function(self.handle.get_raw(),Box::into_raw(Box::new(value.map(|data| {ThreadsafeFunctionCallJsBackData {data,call_variant: ThreadsafeFunctionCallVariant::Direct,callback: Box::new(|_d: Result| Ok(())),}}))).cast(),mode.into(),)}.into()})
}pub fn call_with_return_value Result<()>>(&self,value: Result,mode: ThreadsafeFunctionCallMode,cb: F,
) -> Status {self.handle.with_read_aborted(|aborted| {if aborted {return Status::Closing;}unsafe {sys::napi_call_threadsafe_function(self.handle.get_raw(),Box::into_raw(Box::new(value.map(|data| {ThreadsafeFunctionCallJsBackData {data,call_variant: ThreadsafeFunctionCallVariant::WithCallback,callback: Box::new(move |d: Result| {d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb))}),}}))).cast(),mode.into(),)}.into()})
}

call_with_return_value的使用方式如下:

#[napi]
pub fn call_threadsafe_function(callback: ThreadsafeFunction) -> Result<()> {callback.call_with_return_value(Ok(1), ThreadsafeFunctionCallMode::Blocking, move |result: u32| {println!("callback: {result:?}");Ok(())});Ok(())
}

可以看出,JS 回调函数的返回值是在call_with_return_value的第三个回调函数参数中获取到的,这就导致如果我们需要依赖这个 JS 函数返回值的话,我们后续的逻辑代码只能写在call_with_return_value的第三个回调函数参数中,对我们的代码逻辑书写造成诸多不便,代码可读性降低,所以推荐使用call_async方法来执行 JS 函数,并获取参数。

使用call_async获取 JS 函数返回值

call_async的实现可以看出,它首先使用了tokio创建了一个 one-shot 通道,让 JS 函数以不阻塞的方式异步运行,并在执行完成后通过sender 发送操作结果,而使用receiver进行等待执行结果,并将结果返回,同时要使用call_async方法,需要在Cargo.toml中为napi依赖打开tokio_rt特性。

#[cfg(feature = "tokio_rt")]
pub async fn call_async(&self, value: Result) -> Result {let (sender, receiver) = tokio::sync::oneshot::channel::>();self.handle.with_read_aborted(|aborted| {if aborted {return Err(crate::Error::from_status(Status::Closing));}check_status!(unsafe {sys::napi_call_threadsafe_function(self.handle.get_raw(),Box::into_raw(Box::new(value.map(|data| {ThreadsafeFunctionCallJsBackData {data,call_variant: ThreadsafeFunctionCallVariant::WithCallback,callback: Box::new(move |d: Result| {sender.send(d.and_then(|d| D::from_napi_value(d.0.env, d.0.value))).map_err(|_| {crate::Error::from_reason("Failed to send return value to tokio sender")})}),}}))).cast(),ThreadsafeFunctionCallMode::NonBlocking.into(),)},"Threadsafe function call_async failed")})?;receiver.await.map_err(|_| {crate::Error::new(Status::GenericFailure,"Receive value from threadsafe function sender failed",)}).and_then(|ret| ret)
}

可见call_async使用时将引入 Rust 的异步编程,我们可以使用async/await关键字来进行调用,使用方式如下:

#[napi]
pub async fn call_threadsafe_function(callback: ThreadsafeFunction) -> Result {let result = match callback.call_async::(Ok(1)).await {Ok(res) => res,Err(e) => {println!("Error: {}", e);0}};println!("result: {result:?}");Ok(result)
}

此时生成的 JS 函数定义为如下,可以看出callThreadsafeFunction变成了一个异步函数:

export function callThreadsafeFunction(callback: (err: Error | null, value: number) => any): Promise<number>

所以在 JS 中调用方式及输出结果为:

const result = await callThreadsafeFunction((err, value) => {return value + 1
})
console.log(result)// 输出结果
// result: 2
// 2

正确处理 JS 函数的返回值

从前面call_async的实现可以看出,call_async返回的数据,也即 JS 函数返回值需要满足如下泛型约束D: 'static + FromNapiValue,而 napi-rs 默认会为数值、字符串、布尔等基本 JS 数据类型实现FromNpiValuetrait,但是如果我们的 JS 回调想要返回一个对象时,则需要自己手动实现FromNpiValuetrait,这样可以让call_async获取到 JS 返回数据时自动调用FromNpiValuetrait 的from_napi_value方法将 JS 返回数据转换为 Rust 的数据格式,以下是一个简单的示例。

假如需要在 Rust 调用一个 JS 函数,JS 函数会返回一个对象,包含三个字段:

{setPageName?: string,changeExt?: boolean,setSubPkgName?: string
}

我们需要在 Rust 中获取到返回的对象,并转为 Rust 数据,那么首先我们可以定义一个类似的数据结构:

#[derive(Debug)]
pub struct JSReturnObject {pub set_page_name: Option,pub change_ext: Option,pub set_sub_pkg_page_name: Option,
}

同时为它实现FromNpiValuetrait 就可以了:

impl FromNapiValue for JSReturnObject {unsafe fn from_napi_value(env: napi_env, napi_val: napi_value) -> Result {let obj = JsObject::from_napi_value(env, napi_val)?;let mut js_return_object = JSReturnObject {set_page_name: None,change_ext: None,set_sub_pkg_page_name: None,};let has_set_page_name = obj.has_named_property("setPageName")?;let has_change_ext = obj.has_named_property("changeExt")?;let has_set_sub_pkg_page_name = obj.has_named_property("setSubPkgName")?;if has_set_page_name {js_return_object.set_page_name = Some(obj.get_named_property::("setPageName")?);}if has_set_sub_pkg_page_name {js_return_object.set_sub_pkg_page_name = Some(obj.get_named_property::("setSubPkgName")?);}if has_change_ext {js_return_object.change_ext = Some(obj.get_named_property::("changeExt")?);}Ok(js_return_object)}
}

在上述代码中,先调用JsObject::from_napi_value方法将传入数据转为JsObject,然后调用

JsObjecthas_named_property方法获取到对应的属性值,经过处理后可以构建出JSReturnObject结构体数据,并进行返回。而使用的时候,为call_async指定泛型参数类型为JSReturnObject,接下来就可以获取到 JS 返回值进行处理了。

let result: JSReturnObject = js_handler.call_async(Ok(options.clone())).await.with_context(|| format!("模板自定义函数调用失败: {}", file_relative_path))?;

使用 VSCode 进行调试

我们可以使用 VSCode 来调试我们的 napi-rs 应用,我们可以参考Taro 项目,在项目的 .vscode 目录下新增 launch.json 配置如下:

{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"type": "lldb", // 调试器类型,这里指定为lldb,通常用于C/C++/Rust等语言"request": "launch", // 请求类型,可以是'launch'或'attach','launch'表示启动一个新的调试会话"name": "debug-init", // 配置名称,显示在VS Code的启动配置下拉菜单中"sourceLanguages": ["rust"], // 指定源码语言,此处为Rust"program": "node", // 要调试的程序,这里是指Node.js的可执行文件"args": [// 程序参数,这里指定了使用node运行taro-cli包的初始化命令,创建一个名为test_pro的新项目"${workspaceFolder}/packages/taro-cli/bin/taro","init","test_pro"],"cwd": "${workspaceFolder}", // 当前工作目录,这里指工作区根目录"preLaunchTask": "build binding debug", // 调试前需要执行的任务的名称,这里指定了一个任务以在调试前构建项目"postDebugTask": "remove test_pro" // 调试后需要执行的任务的名称,此处指定了一个任务以在调试后清理或删除test_pro项目},]
}

在上述配置中,指定调试器类型为lldb,启动一个新的调试会话来调试我们用 Rust 编写的程序,该程序主要通过 Node.js 来执行一个初始化新项目 test_pro 的命令,在调试开始前后会飞别执行 Rust binding 的构建以及 test_pro 项目的删除。

然后在要调试的代码处添加断点,然后执行调试即可。

调试

构建发布

napi-rs 的项目模板默认基于 Github Action 来实现自动构建产物及发布,并且已经有相当完整的配置了,从Github Action配置文件中可以看到 CI 具体执行的任务,CI 任务首先会执行 package.json 中的构建命令,构建出各个端的 binding,并会actions/upload-artifact@v3action 将构建产物上传,然后会对构建产物执行相关测试,测试通过后会将构建产物下载下来,并执行artifacts命令将构建产物移动到目的文件夹下,最后会进行发布,当 git 提交信息为semver规范版本号时,将会触发 CI 发布,将包发到 NPM 中去。

$ git commit -m '0.0.1'

前面提到我们一般只需要针对x86_64-apple-darwinaarch64-apple-darwinx86_64-pc-windows-msvcx86_64-unknown-linux-gnu这四个平台进行构建,所以我们可以调整 Github Action 配置,去掉不需要构建的平台以提升 CI 速度。

此外,当我们有特殊需求的时候,例如不需要重新生成胶水 JS 代码、需要将构建产物移动到其他目录(默认是当前目录下的 npm 目录)下等等,可以查看@napi-rs/cli的文档进行相应调整。

不需要重新生成胶水 JS 代码,可以通过在napi build命令下添加--no-js实现:

"scripts": {..."build": "napi build --platform --release --no-js --dts binding.d.ts","build:debug": "napi build --platform --no-js --dts binding.d.ts"...
}

需要将构建产物移动到其他目录,可以通过在napi artifacts命令下添加 --cwd 和 --npm-dir 参数来实现,前者指定工作目录,后者指定要移动的目录的相对路径

"scripts": {..."artifacts": "napi artifacts --npm-dir ../../npm2 --cwd ./",...
}

总结

Rust 在前端领域的应用无疑将成为未来的重要发展趋势,随着越来越多的公司和团队开始投入到这一领域,我们看到了 Rust 在前端研发生态构建中的独特优势和潜力,Rust 的高效性和安全性使其成为优化 Node 工具的理想选择。本文简单介绍了如何使用 NAPI-RS 来开发、调试和发布 Node 扩展,可以有效地优化我们的开发工具,并提升其性能。

在未来,我们可以预见 Rust 与前端结合的可能性将会更加广泛。随着 WebAssembly(WASM)的发展,我们可以期待 Rust 将在前端应用的性能优化、复杂应用的开发以及多线程等领域发挥更大的作用。同时,Rust 的出色的内存管理和错误处理机制也将帮助前端开发者构建更加健壮、安全的应用。

当然,Rust 与前端的结合并不仅仅限于性能优化,Rust 的优秀特性,如模式匹配、类型推断和零成本抽象,也为前端开发带来了新的编程范式和思维方式,这将有助于提升前端代码的可读性和可维护性,为前端开发提供了新的思考角度和工具,并可能引领前端开发进入一个全新的阶段。

作者:京东零售 李伟涛

来源:京东云开发者社区 转载请注明来源

相关文章:

解锁前端新潜能:如何使用 Rust 锈化前端工具链

前言 近年来&#xff0c;Rust的受欢迎程度不断上升。首先&#xff0c;在操作系统领域&#xff0c;Rust 已成为 Linux 内核官方认可的开发语言之一&#xff0c;Windows 也宣布将使用 Rust 来重写内核&#xff0c;并重写部分驱动程序。此外&#xff0c;国内手机厂商 Vivo 也宣布…...

vite前端工具链,为开发提供极速响应

一、概念 Vite是一个高性能的分布式智能合约平台。它使用了一种名为“异步架构”的设计&#xff0c;能够支持高吞吐量和低延迟的交易处理。Vite采用了基于DAG&#xff08;有向无环图&#xff09;的账本结构&#xff0c;可以实现并行处理多个交易&#xff0c;并且具有快速确认的…...

linux系统nginx做负载均衡

负载均衡 作用upstream配置负载均衡算法配置分类热备轮询加权轮询ip_hash 负载均衡配置状态参数nginx配置7层协议及4层协议七层协议做负载均衡四层协议做负载均衡 会话保持ip_hashsticky_cookie_insertjvm_route 作用 负载均衡&#xff08;Load Balance&#xff0c;简称 LB&am…...

Tensor Core的一些概念理解

英伟达的GPU产品架构发展如下图&#xff0c;Tensor Core是从2017年的Volta架构开始演变的针对AI模型大量乘加运算的特殊处理单元。本文主要梳理一些关于Tensor Core的一些基础概念知识。 什么是混合精度&#xff1f; 混合精度在底层硬件算子层面&#xff0c;使用半精度&#xf…...

Git与VScode联合使用详解

目录 Git与VScode联合使用 方式一 1. 用vscode打开文件夹&#xff0c;如图点击初始化仓库&#xff0c;把此仓库初始为git仓库。 2. 提交文件到本地仓库 3. vscode与github账号绑定 4. 在github中建立远程仓库 5. 本地仓库与远程仓库绑定 方式二 1. 在github上建立远程仓…...

SQL Server 加密 view文本

CREATE VIEW dbo.View_building WITH ENCRYPTION AS SELECT * FROM Building_Temp; GO 注意&#xff1a; 加密後就看不到VIEW文本了&#xff0c;修改 ALTER VIEW dbo.View_building WITH ENCRYPTION AS –修改後的VIEW 文本 GO 或者刪除再新增。 所以&#xff0c;要另備份原V…...

Linux查看物理CPU个数、核数、逻辑CPU个数

文章目录 总核数总逻辑CPU数查看物理CPU个数查看每个物理CPU中core的个数(即核数)查看逻辑CPU的个数 总核数 总核数 物理CPU个数 X 每颗物理CPU的核数 总逻辑CPU数 总逻辑CPU数 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 查看物理CPU个数 cat /proc/cpuinfo| grep “…...

python使用单例模式加载config.ini配置文件

在Python中&#xff0c;可以使用单例模式来加载和管理配置文件。下面是一个示例代码&#xff1a; import configparserclass ConfigLoader:__instance Nonedef __init__(self):if ConfigLoader.__instance is not None:raise Exception("ConfigLoader is a singleton cl…...

牛刀小试---二分查找(C语言)

题目&#xff1a;在给定的升序数组中查找指定的数字n&#xff0c;并输出其下标 代码举例&#xff1a; #include <stdio.h> int main() {int arr[] { 1,2,3,4,5,6,7,8,9,10 };//给定的升序数组int left 0;//定义左下标int right sizeof(arr) / sizeof(arr[0]) - 1;//…...

k8s-数据卷

存储卷----数据卷 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的&#xff0c;delete&#xff0c;k8s用控制创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态也会恢复到初识状态 一旦容器回到初始状态&#xff0c;所有得分后天编辑的文件…...

Linux学习记录——사십삼 高级IO(4)--- Epoll型服务器

文章目录 1、理解Epoll和对应接口2、实现 1、理解Epoll和对应接口 poll依然需要OS去遍历所有fd。一个进程去多个特定的文件中等待&#xff0c;只要有一个就绪&#xff0c;就使用select/poll系统调用&#xff0c;让操作系统把所有文件遍历一遍&#xff0c;哪些就绪就加上哪些fd…...

6.4、SDN在云数据中心的应用案例分析

云数据中心中的虚拟子网包含网关和IP网段,IP分配给各个服务器,服务器间能够互相通信或通过网关访问外部网络。 在SDN云数据中心内,用户可以随时订购任意网段的虚拟子网,而且这些子网是可以在不同用户之间复用的,也就是说,不同用户可以使用相同的私有网段。 SDN云数据中心…...

SpringBoot整合ES

1.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> <version>2.6.3</version> </dependency> 2.config配置文件 Configu…...

Pandas实战100例 | 案例 10: 应用函数 - 使用 `apply`

案例 10: 应用函数 - 使用 apply 知识点讲解 Pandas 的 apply 函数是一个非常强大的工具&#xff0c;允许你对 DataFrame 中的行或列应用一个函数。这对于复杂的数据转换和计算非常有用。你可以使用 apply 来执行任意的函数&#xff0c;这些函数可以是自定义的&#xff0c;也…...

《C++大学教程》4.13汽油哩数

题目: 每位司机都关心自己车辆的行车里程数。有位司机通过记录每次出行所行驶的英里数和用油的加仑数来跟踪他多次出车的情况。请开发一个C程序&#xff0c;它使用一条while语句输入每次出车的行驶英里数和加油量。该程序应计算和显示每次出车所得到的每加仑行驶英里数&#x…...

OpenGL排坑指南—贴图纹理绑定和使用

一、前言 在OpenGL学习 的纹理这一章中讲述了纹理贴图的使用方式&#xff0c;主要步骤是先创建一个纹理的对象&#xff0c;和创建顶点VAO类似&#xff0c;然后就开始绑定这个纹理&#xff0c;最后在循环中使用&#xff0c;有时候可能还要用到激活纹理单元的函数。然而&#xff…...

Electron中 主进程(Main Process)与 渲染进程 (Renderer Process) 通信的方式

1. 渲染进程向主进程通信 修改 html 文件内容 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><!-- 解决控制…...

企业微信forMAC,如何左右翻动预览图片

1、control commandshifd 进入企业微信的debug调试模式 2、按照如下步骤选择 3、重启企业微信...

Android Firebase (FCM)推送接入

官方文档&#xff1a; 向后台应用发送测试消息 | Firebase Cloud Messaging 1、根级&#xff08;项目级&#xff09;Gradlegradle的dependencies中添加&#xff1a; dependencies {...// Add the dependency for the Google services Gradle pluginclasspath com.google.gm…...

Neo4j恢复

主要记录windows环境下从备份文件中恢复Neo4j&#xff0c; Linux环境同理 备份在上一篇中有介绍&#xff0c;参考: Neo4j备份-CSDN博客 误删数据 为了模拟误删除场景&#xff0c;我们查询Person&#xff0c;并模拟误操作将其进行删除&#xff1b; match(p:Person) return …...

ZZULIOJ 1114: 逆序

题目描述 输入n&#xff08;1<n<10&#xff09;和n个整数&#xff0c;逆序输出这n个整数。 输入 输入n&#xff08;1<n<10&#xff09;&#xff0c;然后输入n个整数。 输出 逆序输出这n个整数&#xff0c;每个整数占4列&#xff0c;右对齐。 样例输入 Copy …...

Linux前后端项目部署

目录 1.jdk&tomcat安装 配置并且测试jdk安装 修改tomcat 配置文件 登入tomcat 发布 安装mysql 导入sql数据 发布项目war包 redis安装 nginx安装 配置nginx域名映射 部署前端项目 centos 7的服务安装 安装jdk 安装tomcat 安装Mysql 安装redis 安装nginx 前后…...

GPT-4与DALL·E 3:跨界融合,开启绘画与文本的新纪元

在人工智能的发展浪潮中&#xff0c;MidTool&#xff08;https://www.aimidtool.com/&#xff09;的GPT-4与DALLE 3的集成代表了一个跨越式的进步。这一集成不仅仅是技术的结合&#xff0c;更是艺术与文字的完美融合&#xff0c;它为创意产业带来了革命性的变革。本文将探讨GPT…...

聊聊PowerJob的Alarmable

序 本文主要研究一下PowerJob的Alarmable Alarmable tech/powerjob/server/extension/Alarmable.java public interface Alarmable {void onFailed(Alarm alarm, List<UserInfoDO> targetUserList); }Alarmable接口定义了onFailed方法&#xff0c;其入参为alarm及tar…...

系列三十五、获取Excel中的总记录数

一、获取Excel中的总记录数 1.1、概述 使用EasyExcel开发进行文件上传时&#xff0c;通常会碰到一个问题&#xff0c;那就是Excel中的记录数太多&#xff0c;使用传统的方案进行文件上传&#xff0c;很容易就超时了&#xff0c;这时可以通过对用户上传的Excel中的数量进行限制…...

VMware workstation安装debian-12.1.0虚拟机并配置网络

VMware workstation安装debian-12.1.0虚拟机并配置网络 Debian 是一个完全自由的操作系统&#xff01;Debian 有一个由普罗大众组成的社区&#xff01;该文档适用于在VMware workstation平台安装debian-12.1.0虚拟机。 1.安装准备 1.1安装平台 Windows 11 1.2软件信息 软…...

centos下系统全局检测工具dstat使用

目录 一&#xff1a;没有需要安装 二&#xff1a;dstat命令参数 三、监测界面各参数含义&#xff08;部分&#xff09; 四、dstat的高级用法 一&#xff1a;没有需要安装 yum install dstat 二&#xff1a;dstat命令参数 有默认选项&#xff0c;执行dstat命令不加任何参数…...

无人机群ros通信

单架无人机与地面站通信 在一个局域网内获取无人机的机载电脑ip 通过地面站ssh到机载电脑&#xff0c;实现通信 多架无人机与地面站通信 在ROS基础上&#xff0c;配置主机和从机&#xff0c;实现主机和从机的话题联通 配置hosts 在主机和从机的/etc/hosts文件中&#xff0c…...

LeetCode刷题:142. 环形链表 II

题目&#xff1a; 是否独立解决&#xff1a;否&#xff0c;参考了解题思路解决问题&#xff0c;思考了用快慢指针&#xff0c;栈&#xff0c;统计链表数量定位尾巴节点&#xff08;因为是环形链表所以是死循环&#xff0c;链表数量用while循环统计不出来&#xff09;都没解决 解…...

Laravel 使用rdkafka_laravel详细教程(实操避坑)

一、选择rdkafka 首先要看版本兼容问题&#xff0c;我的是Laravel5.6&#xff0c;PHP是7.3.13&#xff0c;所以需要下载兼容此的rdkafka&#xff0c;去 Packagist 搜索 kafka &#xff0c;我用的是 enqueue/rdkafka选择里面0.10.5版本&#xff0c; 二、安装rdkafka 在 Larav…...