RPC妙用:跨语言编程服务控制

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. 代码实现

代码实现分为三部分:

  1. RPC服务端和客户端
  2. RPC服务启动和停止逻辑
  3. Flutte端线程控制逻辑

a. RPC服务端和客户端

这部分的代码,主体借用上一篇关于QUIC-RPC的文章,代码模块结构无需变动,只需要在对应模块添加一个关闭RPC服务器的API定义和逻辑处理即可。

以下是传送门: QUIC-RPC:Rust分布式通信利器

首先在proto模块添加一个Shutdown-RPC的Req->Resp结构:

ShutdownAPI采用.rpc(1 req -> 1 res)模式即可,关于RPC处理模式可以参考上边链接介绍。

  1. 定义Request结构体
  2. 定义Response结构体
  3. 实现RpcMsg<RpcService>特性
  4. 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代码和后端业务的轻耦合。

QUIC-RPC: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_servicestop_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这个第三方库处理子线程的操作,使用很便捷。

easy_isolate | Dart package

创建线程处理函数逻辑

rpc_ctl_worker.dart

首先定义了操作种类枚举Operate,然后定义一个操作事件OperateEvent,作为主线程和子线程通信的协议

rpcCtlWorkerIsolateMessageHandler中调用的startServicestopService就是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

相关推荐
初遇你时动了情1 小时前
dart常用语法详解/数组list/map数据/class类详解
数据结构·flutter·list
OldBirds1 小时前
Flutter element 复用:隐藏的风险
flutter
Source.Liu2 小时前
【PhysUnits】15.5 引入P1后的标准化表示(standardization.rs)
rust
爱意随风起风止意难平2 小时前
002 flutter基础 初始文件讲解(1)
学习·flutter
OldBirds5 小时前
理解 Flutter Element 复用
flutter
xq95275 小时前
flutter 带你玩转flutter读取本地json并展示UI
flutter
hepherd9 小时前
Flutter - 原生交互 - 相机Camera - 01
flutter·ios·dart
ailinghao12 小时前
单例模式的类和静态方法的类的区别和使用场景
flutter·单例模式
爱意随风起风止意难平12 小时前
005 flutter基础,初始文件讲解(4)
学习·flutter
Jim-zf12 小时前
Flutter 嵌套H5 传参数
java·开发语言·flutter