在 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",
        "//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::MonotonicTime::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::MonotonicTime::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::MonotonicTime::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::MonotonicTime::INFINITE).context("error receiving events")?;
    println!("Received OnString event for string {:?}", response);

    Ok(())
}

connect_channel_to_service 会将提供的通道端绑定到指定的 服务。在后台,此调用会触发在客户端上启动的一系列事件,并通过上一个教程中的服务器代码进行跟踪:

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

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

  • 在服务器进程中收到此请求后 它会唤醒 async::Executor 执行器,并告知其 ServiceFs 任务现在可以 进度,应该运行。
  • ServiceFs 被唤醒,在进程的启动句柄上看到可用请求,并且 在 (service_name, service_startup_func) 列表中查找所请求服务的名称 通过调用 add_serviceadd_fidl_service 等方法提供。如果匹配的 service_name 它使用提供的通道调用 service_startup_func,以连接到新服务。
  • 使用 RequestStream 调用 IncomingService::Echo Echo FIDL 协议的(类型化通道),该协议已在 add_fidl_service 中注册。通过 传入的请求渠道存储在 IncomingService::Echo 中,并添加到 传入请求。for_each_concurrentServiceFs 转换为类型的 Stream IncomingService。系统会为流中的每个条目运行处理程序,该处理程序将与传入的 请求和分派给 run_echo_server。通过对 的每次调用生成的 Future 当 ServiceFs 流被 await 处理时,run_echo_server 会并发运行。
  • 在通道上发送请求后,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::MonotonicTime::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::MonotonicTime::INFINITE).context("error receiving events")?;
    println!("Received OnString event for string {:?}", response);

    Ok(())
}

在收到服务器响应之前,对 echo_string 的调用将一直处于阻塞状态,因此 它使用超时参数作为最后一个参数。

另一方面,由于 SendString,对 send_string 的调用没有超时参数 没有响应。采用当前服务器实现时,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