在 Rust 中实现同步 FIDL 客户端

前提条件

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

概览

本教程实现了 FIDL 协议的客户端,并针对在前一个教程中创建的服务器运行该客户端。本教程中的客户端是同步客户端。我们提供了针对异步客户端的备用教程

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

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

创建组件

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

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

    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",
        "//src/lib/fuchsia-component",
        "//src/lib/zircon/rust:fuchsia-zircon",
        "//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;
    use fuchsia_zircon as zx;
    

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

其中一个新依赖项是 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::Time::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::Time::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::Time::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::Time::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,以连接到新服务。
  • IncomingService::Echo 通过已在 add_fidl_service 注册的 Echo FIDL 协议的 RequestStream(类型化通道)调用。传入请求渠道存储在 IncomingService::Echo 中,并添加到传入请求流中。for_each_concurrent 会将 ServiceFs 用作 IncomingService 类型的 Stream。系统会针对流中的每个条目运行处理程序,该处理程序会对传入请求进行匹配并分派给 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::Time::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::Time::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 功能从服务器路由到客户端。

  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"

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

ffx component destroy /core/ffx-laboratory:echo_realm