在 Rust 中实现同步 FIDL 客户端

前提条件

本教程假定您熟悉编写和运行 Fuchsia 组件以及实现 FIDL 服务器,这两者均在 FIDL 服务器教程中有所介绍。如需查看完整的 FIDL 教程集,请参阅概览

概览

本教程将实现一个 FIDL 协议客户端,并针对在上一教程中创建的服务器运行该客户端。本教程中的客户端是同步的。我们提供了一个替代教程,介绍了如何使用异步客户端。

如果您想自行编写代码,请删除以下目录:

rm -r examples/fidl/rust/client_sync/*

创建组件

examples/fidl/rust/client_sync 中创建一个新的组件项目:

  1. main() 函数添加到 examples/fidl/rust/client_sync/src/main.rs

    fn main() {
      println!("Hello, world!");
    }
    
  2. examples/fidl/rust/client_sync/BUILD.gn 中声明客户端的目标:

    import("//build/components.gni")
    import("//build/rust/rustc_binary.gni")
    
    
    # Declare an executable for the client.
    rustc_binary("bin") {
      name = "fidl_echo_rust_client_sync"
      edition = "2021"
    
      sources = [ "src/main.rs" ]
    }
    
    fuchsia_component("echo-client") {
      component_name = "echo_client"
      manifest = "meta/client.cml"
      deps = [ ":bin" ]
    }
    
  3. examples/fidl/rust/client_sync/meta/client.cml 中添加组件清单:

    {
        include: [ "syslog/client.shard.cml" ],
    
        // Information about the program to run.
        program: {
            // Use the built-in ELF runner.
            runner: "elf",
    
            // The binary to run for this component.
            binary: "bin/fidl_echo_rust_client_sync",
        },
    
        // Capabilities used by this component.
        use: [
            { protocol: "fuchsia.examples.Echo" },
        ],
    }
    
    
  4. 创建组件后,请确保可以将其添加到 build 配置中:

    fx set core.x64 --with //examples/fidl/rust/client_sync:echo-client
    
  5. 构建 Fuchsia 映像:

    fx build
    

修改 GN 依赖项

  1. 将以下依赖项添加到 rustc_binary

      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_rust",
        "//sdk/rust/zx",
        "//src/lib/fuchsia-component",
        "//third_party/rust_crates:anyhow",
      ]
    
    
  2. 然后,将其导入 main.rs

    use anyhow::{Context as _, Error};
    use fidl_fuchsia_examples::{EchoEvent, EchoMarker};
    use fuchsia_component::client::connect_to_protocol_sync;
    
    

服务器教程中介绍了这些依赖项。

一个新依赖项是 fuchsia-zircon,它是一个 crate,包含用于执行 Zircon 内核系统调用的类型安全绑定。在此示例中,crate 用于创建通道。

连接到服务器

本部分中的步骤说明了如何向 main() 函数添加代码,该函数用于将客户端连接到服务器并向服务器发出请求。

初始化通道

fn main() -> Result<(), Error> {
    // Connect to the Echo protocol, returning a synchronous proxy
    let echo =
        connect_to_protocol_sync::<EchoMarker>().context("Failed to connect to echo service")?;

    // Make an EchoString request, with no timeout for receiving the response
    let res = echo.echo_string("hello", zx::MonotonicInstant::INFINITE)?;
    println!("response: {:?}", res);

    // Make a SendString request
    echo.send_string("hi")?;
    // Wait for a single OnString event.
    let EchoEvent::OnString { response } =
        echo.wait_for_event(zx::MonotonicInstant::INFINITE).context("error receiving events")?;
    println!("Received OnString event for string {:?}", response);

    Ok(())
}

此通道将用于客户端和服务器之间的通信。

连接到服务器

fn main() -> Result<(), Error> {
    // Connect to the Echo protocol, returning a synchronous proxy
    let echo =
        connect_to_protocol_sync::<EchoMarker>().context("Failed to connect to echo service")?;

    // Make an EchoString request, with no timeout for receiving the response
    let res = echo.echo_string("hello", zx::MonotonicInstant::INFINITE)?;
    println!("response: {:?}", res);

    // Make a SendString request
    echo.send_string("hi")?;
    // Wait for a single OnString event.
    let EchoEvent::OnString { response } =
        echo.wait_for_event(zx::MonotonicInstant::INFINITE).context("error receiving events")?;
    println!("Received OnString event for string {:?}", response);

    Ok(())
}

connect_channel_to_service 会将所提供的通道端点绑定到指定的服务。在后台,此调用会触发一系列事件,这些事件从客户端开始,并跟踪上一个教程中的服务器代码:

  • 向组件框架发出请求,其中包含要连接的服务的名称以及通道的另一端。服务的名称是使用 EchoMarker 模板参数的 SERVICE_NAME 隐式获取的,这与服务器端确定服务路径的方式类似。
  • 此客户端对象由 connect_to_protocol 返回。

在后台,系统会将对组件框架的请求路由到服务器:

  • 当服务器进程中收到此请求时,它会唤醒 async::Executor 执行器,并告知它 ServiceFs 任务现在可以取得进展并应运行。
  • ServiceFs 会唤醒,查看进程的启动句柄上可用的请求,并在通过调用 add_serviceadd_fidl_service 等提供的 (service_name, service_startup_func) 列表中查找请求的服务的名称。如果存在匹配的 service_name,则使用提供的通道调用 service_startup_func 以连接到新服务。
  • 使用已向 add_fidl_service 注册的 Echo FIDL 协议的 RequestStream(类型化通道)调用 IncomingService::Echo。传入请求通道存储在 IncomingService::Echo 中,并添加到传入请求流中。for_each_concurrent 会将 ServiceFs 转换为类型为 IncomingServiceStream。系统会针对数据流中的每个条目运行一个处理脚本,该脚本会与传入请求匹配并调度到 run_echo_server。对 ServiceFs 流执行 await 时,每次调用 run_echo_server 产生的 Future 会并发运行。
  • 在通道上发送请求后,Echo 服务的通道变为可读,这会唤醒 run_echo_server 正文中的异步代码。

向服务器发送请求

代码会向服务器发出两个请求:

  • EchoString 请求
  • SendString 请求
fn main() -> Result<(), Error> {
    // Connect to the Echo protocol, returning a synchronous proxy
    let echo =
        connect_to_protocol_sync::<EchoMarker>().context("Failed to connect to echo service")?;

    // Make an EchoString request, with no timeout for receiving the response
    let res = echo.echo_string("hello", zx::MonotonicInstant::INFINITE)?;
    println!("response: {:?}", res);

    // Make a SendString request
    echo.send_string("hi")?;
    // Wait for a single OnString event.
    let EchoEvent::OnString { response } =
        echo.wait_for_event(zx::MonotonicInstant::INFINITE).context("error receiving events")?;
    println!("Received OnString event for string {:?}", response);

    Ok(())
}

echo_string 的调用将被阻塞,直到从服务器收到响应,因此它会将超时参数作为最后一个参数。

另一方面,对 send_string 的调用没有超时参数,因为 SendString 没有响应。如果采用当前服务器实现,系统会在收到此请求后向客户端发送 OnString 事件。不过,同步 Rust 绑定不支持处理事件。

绑定参考文档介绍了如何生成这些方法,Fuchsia rustdoc 包含生成的 FIDL crate 的文档。

运行客户端

为了让客户端和服务器能够使用 Echo 协议进行通信,组件框架必须将 fuchsia.examples.Echo capability 从服务器路由到客户端。在本教程中,我们提供了一个 realm 组件来声明适当的 capability 和路由。

探索 Realm 组件的完整源代码。
  1. 配置 build 以包含提供的软件包,其中包含 echo 领域、服务器和客户端:

    fx set core.x64 --with //examples/fidl/rust:echo-rust-client-sync
    
  2. 构建 Fuchsia 映像:

    fx build
    
  3. 运行 echo_realm 组件。这将创建客户端和服务器组件实例并路由功能:

    ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-rust-client-sync#meta/echo_realm.cm
    
  4. 启动 echo_client 实例:

    ffx component start /core/ffx-laboratory:echo_realm/echo_client
    

当客户端尝试连接到 Echo 协议时,服务器组件会启动。您应该会在设备日志 (ffx log) 中看到类似于以下内容的输出:

[echo_server][][I] Listening for incoming connections...
[echo_server][][I] Received EchoString request for string "hello"
[echo_server][][I] Response sent successfully
[echo_client][][I] response: "hello"
[echo_server][][I] Received SendString request for string "hi"
[echo_server][][I] Event sent successfully
[echo_client][][I] Received OnString event for string "hi"

终止 Realm 组件以停止执行并清理组件实例:

ffx component destroy /core/ffx-laboratory:echo_realm