01. 背景
随着业务的不断拓展和技术栈的日益丰富,我们常常面临一个挑战:如何让不同语言编写的服务之间高效地进行通信和协作? 这时候,远程过程调用(RPC)技术就如同一位得力助手,为我们解决了跨语言编程中的服务控制难题。
笔者目前主要深入研究Flutter和Rust结合开发的项目实践,之前的文章里介绍了通过Flutter Rust Bridge(FRB)作为两种语言的胶水层,来实现Rust和Flutter的数据和逻辑互通。
对于简单的一次性跨语言函数调用,实现起来和单语言编程几乎没什么区别。即使对于高级场景下的Stream流数据处理,虽然稍微复杂一些但多学习几个相关API也可以轻松实现。 对于这部分内容参考前文:
如何用FRB(flutter_rust_bridge)快速搭建Flutter+Rust混编项目
无惧阻塞!Flutter从Rust丝滑接收Stream数据流
而接下来要介绍的内容--服务控制,和上述两种场景有点儿关联,但区别也很明显。
首先,前两种场景中,无论是一次性函数调用还是Stream流式数据处理函数,最终都会在任务结束后退出执行,作为后端被调用方,声明周期很短。
而服务是一般作为常驻后台的线程或进程存在的,随时等待客户端的请求并作出响应,除此之外,还需要能支持远程启动重启退出状态控制。
02. 方案分析
实现Flutter端控制Rust端服务状态有两种方案,都需要借助Dart线程isolate来实现,也就是Flutter端也要启动一个常驻线程来负责和Rust端服务函数通信交互。
a. 方案1
子线程启动,则服务启动。 如需停止服务,则由主线程Kill掉子线程,子线程结束服务也跟着停止。

b. 方案2
子线程启动时,默认不启动服务。 由主线程发送信号给子线程,子线程执行启动或停止服务操作。

以上两种方案很容易看出方案1控制服务状态的方式比较暴力,相当于以Kill子线程的方式把停止服务的动作交给了操作系统。
这种方式对服务端来说,就像电脑关机直接拔电源一样,完全没有结束前缓冲过渡和清理的机会。
而方案2的处理方式温和许多,兼顾了控制和优雅退出,所以本文下边以方案2进行讲解实现过程和逻辑。
03. 代码实现
代码实现分为三部分:
- RPC服务端和客户端
- RPC服务启动和停止逻辑
- Flutte端线程控制逻辑
a. RPC服务端和客户端
这部分的代码,主体借用上一篇关于QUIC-RPC的文章,代码模块结构无需变动,只需要在对应模块添加一个关闭RPC服务器的API定义和逻辑处理即可。
以下是传送门: QUIC-RPC:Rust分布式通信利器
首先在proto
模块添加一个Shutdown-RPC的Req->Resp结构:
Shutdown
API采用.rpc
(1 req -> 1 res)模式即可,关于RPC处理模式可以参考上边链接介绍。
- 定义Request结构体
- 定义Response结构体
- 实现
RpcMsg<RpcService>
特性- Request和Response两个枚举中添加对应变体
rust
#[derive(Debug, Serialize, Deserialize)]
pub struct ShutdownRequest;
#[derive(Debug, Serialize, Deserialize)]
pub struct ShutdownResponse;
impl RpcMsg<RpcService> for ShutdownRequest {
type Response = ShutdownResponse;
}
scss
#[derive(Debug, Serialize, Deserialize, TryInto, From)]
pub enum Request {
Version(VersionRequest),
ListArticles(ListArticlesRequest),
Shutdown(ShutdownRequest),
}
#[derive(Debug, Serialize, Deserialize, TryInto, From)]
pub enum Response {
Version(VersionResponse),
Article(ArticleResponse),
Shutdown(ShutdownResponse),
}
然后是rpc
模块, Handler
结构添加cancellation_token
字段,用以在RPC请求内部控制取消操作
再添加新的Request模式匹配和处理逻辑函数
rust
#[derive(Clone)]
pub struct Handler {
pub cancellation_token: CancellationToken,
}
rust
pub async fn handle_rpc_request<C>(self, msg: Request, chan: RpcChannel<RpcService, C>) -> Result<(), RpcServerError<C>>
where
C: ChannelTypes<RpcService>
{
println!("Handling RPC request: {:?}", msg);
match msg {
Request::Version(ver)=> chan.rpc(ver, self, Self::get_version).await,
Request::ListArticles(req) => chan.server_streaming(req, self, Self::list_articles).await,
Request::Shutdown(req) => chan.rpc(req, self, Self::shutdown).await,
}
}
此处在收到Shutdown请求后,启动了一个新线程在3秒延迟后执行cancal
操作。
这样处理可以确保客户端发起Shutdown
后还可以正常收到RPC的Response,如果直接取消会在客户端触发EarlyClose的错误。
rust
pub async fn shutdown(self, _msg: ShutdownRequest) -> ShutdownResponse {
let _token = self.cancellation_token.clone();
tokio::spawn(async move {
sleep(Duration::from_secs(3)).await;
_token.cancel();
});
ShutdownResponse
}
b. RPC服务启动和停止逻辑
RPC服务启停逻辑放在FRB项目结构Rust代码部分里,后边可以导出DartAPI给Flutter调用。
FRB的使用参考另一篇文章,传送门: 如何用FRB(flutter_rust_bridge)快速搭建Flutter+Rust混编项目
FRB项目结构里Rust Crate结构参考下图

rust/Cargo.toml
添加依赖
这个
rust-quic-rpc-wrapper
本地依赖实际是下边这个文章里演示代码Crate,按照文章演示步骤可以实现这个Crate。虽然这些代码也可以放在FRB项目的Rust代码部分里,但放在外部单独一个Crate比较清晰,也可以保持FRB里Rust代码和后端业务的轻耦合。
ini
[dependencies]
anyhow = "1.0.98"
flutter_rust_bridge = "=2.9.0"
rust-quic-rpc-wrapper = { path = "../../rust-quic-rpc-wrapper"}
tokio = { version = "1.45", features = ["full"] }
tokio-util = "0.7.15"
在对应rust/src/api
目录,添加rpc_clt
模块
mod.rs
里导出rpc_clt
模块
rust
pub mod simple;
pub mod rpc_clt;
``rpc_clt.rs添加
start_service和
stop_service`函数
start_service
比较简单,就是把外部启动RPC服务器的函数包了一层
stop_service
主要发起了一个RPC请求,通知RPC服务器停止服务
rust
use rust_quic_rpc_wrapper::client::make_rpc_client;
use rust_quic_rpc_wrapper::proto::ShutdownRequest;
use rust_quic_rpc_wrapper::server::start_rpc_server;
use tokio_util::sync::CancellationToken;
pub async fn start_service() -> anyhow::Result<()> {
let cancellation_token = CancellationToken::new();
let _ = start_rpc_server(Some(34567), cancellation_token).await;
Ok(())
}
pub async fn stop_service() -> anyhow::Result<()> {
let client = make_rpc_client(Some(34567)).await.unwrap();
let res = client.rpc(ShutdownRequest).await.expect("Failed to shutdown");
println!("Send shutdown request, received: {:?}", res);
Ok(())
}
执行flutter_rust_bridge_codegen.exe generate
后,会自动生成DartAPI代码函数API,Flutter里可以直接调用。
c. Flutte端线程控制逻辑
Flutter端采用easy_isolate
这个第三方库处理子线程的操作,使用很便捷。
创建线程处理函数逻辑
rpc_ctl_worker.dart
首先定义了操作种类枚举
Operate
,然后定义一个操作事件OperateEvent
,作为主线程和子线程通信的协议
rpcCtlWorkerIsolateMessageHandler
中调用的startService
和stopService
就是Rust代码导出给Flutter的DartAPI函数
kotlin
import 'dart:isolate';
import 'package:easy_isolate/easy_isolate.dart' as easy_isolate;
import 'package:{PROJECT}/src/rust/api/rpc_clt.dart';
import 'package:{PROJECT}/src/rust/frb_generated.dart';
enum Operate {
start,
stop,
}
class OperateEvent {
Operate operate;
OperateEvent(this.operate);
}
rpcCtlWorkerMainMessageHandler(dynamic data, SendPort isolateSendPort){
print('in main func');
}
rpcCtlWorkerIsolateMessageHandler(dynamic data, SendPort isolateSendPort, easy_isolate.SendErrorFunction sendError,)async{
print('in isolate func: ${data}');
try {
await RustLib.init();
} catch (e) {
//
}
if (data is OperateEvent){
if (data.operate == Operate.start) {
await startService();
}else if (data.operate == Operate.stop){
await stopService();
}
}
}
在界面中操作线程控制服务
main.dart
RpcCtlWidget
是独立控制RpcService的组件,三个按钮控制启动线程,启动服务,停止服务
启动线程部分需要注意的地方是,init里传的两个参数是
rpc_ctl_worker.dart
里两个消息处理函数
scala
class RpcCtlWidget extends StatefulWidget {
const RpcCtlWidget({super.key});
@override
State<RpcCtlWidget> createState() => _RpcCtlWidgetState();
}
class _RpcCtlWidgetState extends State<RpcCtlWidget> {
easy_isolate.Worker _worker = easy_isolate.Worker();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: ()async{
await _worker.init(
rpcCtlWorkerMainMessageHandler,
rpcCtlWorkerIsolateMessageHandler,
);
print('Rpc service is ready to start');
},
child: Text("Start Flutter Isolate"),
),
ElevatedButton(
onPressed: () {
_worker.sendMessage(OperateEvent(Operate.start));
},
child: Text("Start Rust Rpc Service"),
),
ElevatedButton(
onPressed: () {
_worker.sendMessage(OperateEvent(Operate.stop));
},
child: Text("Stop Flutter Isolate"),
),
],
),
],
),
);
}
}
04. 运行测试

vbnet
flutter: Rpc service is ready to start
flutter: in isolate func: Instance of 'OperateEvent'
Starting RPC server on 127.0.0.1:34567
flutter: in isolate func: Instance of 'OperateEvent'
Connecting to 127.0.0.1:34567
Check accept request: Shutdown(ShutdownRequest)
Handling RPC request: Shutdown(ShutdownRequest)
Send shutdown request, received: ShutdownResponse
Cancelled
点击对应按钮就能控制线程启动,然后启动服务 或停止服务。
启动后,查看网络端口可以看到已经启动指定端口。

05. 总结
有了RPC的便捷处理,让Flutter和Rust的结合有了更多扩展的空间。
在实际项目中,借助 RPC,我们可以将 Rust 编写的核心业务逻辑,如复杂的加密算法、数据处理模块,部署为独立的服务。Flutter 应用作为前端界面,通过 RPC 协议与 Rust 服务进行通信,既能享受 Flutter 快速构建精美界面的优势,又能利用 Rust 实现高效稳定的后台支持。
本专栏专注Rust和Flutter深度协作实践,欢迎关注和交流。
附:
RPC服务器演示代码库:rust-quic-rpc-wrapper