前提条件
本教程假定您熟悉编写和运行 Fuchsia 组件以及实现 FIDL 服务器,这两者均在 FIDL 服务器教程中有所介绍。如需查看完整的 FIDL 教程集,请参阅概览。
概览
本教程将实现一个 FIDL 协议客户端,并针对在上一教程中创建的服务器运行该客户端。本教程中的客户端是异步的。我们提供了一个适用于同步客户端的替代教程。
如果您想自行编写代码,请删除以下目录:
rm -r examples/fidl/rust/client/*
创建组件
在 examples/fidl/rust/client
中创建一个新的组件项目:
将
main()
函数添加到examples/fidl/rust/client/src/main.rs
:fn main() { println!("Hello, world!"); }
在
examples/fidl/rust/client/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" edition = "2021" sources = [ "src/main.rs" ] } fuchsia_component("echo-client") { component_name = "echo_client" manifest = "meta/client.cml" deps = [ ":bin" ] }
在
examples/fidl/rust/client/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", }, // Capabilities used by this component. use: [ { protocol: "fuchsia.examples.Echo" }, ], }
创建组件后,请确保您可以将其添加到 build 配置中:
fx set core.x64 --with //examples/fidl/rust/client:echo-client
构建 Fuchsia 映像:
fx build
修改 GN 依赖项
将以下依赖项添加到
rustc_binary
:deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_rust", "//src/lib/fuchsia", "//src/lib/fuchsia-component", "//third_party/rust_crates:anyhow", "//third_party/rust_crates:futures", ]
然后,将其导入
main.rs
:use anyhow::{Context as _, Error}; use fidl_fuchsia_examples::{EchoEvent, EchoMarker}; use fuchsia_component::client::connect_to_protocol; use futures::prelude::*;
服务器教程中介绍了这些依赖项。
连接到服务器
本部分中的步骤介绍了如何向 main()
函数添加代码,以将客户端连接到服务器并向其发出请求。
连接到服务器
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Connect to the Echo protocol, which is assumed to be in the component's environment
let echo = connect_to_protocol::<EchoMarker>().context("Failed to connect to echo service")?;
// Make an EchoString request and wait for the response
let res = echo.echo_string("hello").await?;
println!("response: {:?}", res);
// Make a SendString request
echo.send_string("hi")?;
// Wait for a single OnString event
let EchoEvent::OnString { response } =
echo.take_event_stream().next().await.context("error receiving events")??;
println!("Received OnString event for string {:?}", response);
Ok(())
}
在后台,此调用会触发一系列事件,这些事件从客户端开始,并跟踪上一个教程中的服务器代码。
- 初始化客户端对象和通道。客户端对象绑定到通道的任一端。
- 向组件框架发出请求,其中包含要连接到的服务的名称和通道的另一端。服务名称是使用
EchoMarker
模板参数的SERVICE_NAME
隐式获取的,与服务器端确定服务路径的方式类似。 - 此客户端对象由
connect_to_protocol
返回。
在后台,系统会将对组件框架的请求路由到服务器:
- 当服务器进程中收到此请求时,它会唤醒
async::Executor
执行器,并告知它ServiceFs
任务现在可以取得进展并应运行。 ServiceFs
会唤醒,查看进程的启动句柄上可用的请求,并在通过调用add_service
、add_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
转换为类型为IncomingService
的Stream
。系统会针对数据流中的每个条目运行一个处理脚本,该脚本会与传入请求匹配并调度到run_echo_server
。对ServiceFs
流执行await
时,每次调用run_echo_server
产生的 Future 会并发运行。 - 在通道上发送请求后,
Echo
服务的通道变为可读,这会唤醒run_echo_server
正文中的异步代码。
向服务器发送请求
该代码会向服务器发出两个请求:
EchoString
请求SendString
请求
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Connect to the Echo protocol, which is assumed to be in the component's environment
let echo = connect_to_protocol::<EchoMarker>().context("Failed to connect to echo service")?;
// Make an EchoString request and wait for the response
let res = echo.echo_string("hello").await?;
println!("response: {:?}", res);
// Make a SendString request
echo.send_string("hi")?;
// Wait for a single OnString event
let EchoEvent::OnString { response } =
echo.take_event_stream().next().await.context("error receiving events")??;
println!("Received OnString event for string {:?}", response);
Ok(())
}
对 EchoString
的调用会返回一个 Future,该 Future 会解析为服务器返回的响应。如果发送请求或接收响应时出现错误(例如在解码消息时或收到了 epitaph),则返回的 Future 将解析为错误。
另一方面,对 SendString
的调用会返回 Result
,因为它是一种“触发即忘”方法。如果发送请求时出现问题,此方法调用将返回错误。
绑定参考文档介绍了如何生成这些代理方法,Fuchsia rustdoc 包含生成的 FIDL crate 的文档。
处理传入事件
然后,该代码会等待来自服务器的单个 OnString
事件:
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Connect to the Echo protocol, which is assumed to be in the component's environment
let echo = connect_to_protocol::<EchoMarker>().context("Failed to connect to echo service")?;
// Make an EchoString request and wait for the response
let res = echo.echo_string("hello").await?;
println!("response: {:?}", res);
// Make a SendString request
echo.send_string("hi")?;
// Wait for a single OnString event
let EchoEvent::OnString { response } =
echo.take_event_stream().next().await.context("error receiving events")??;
println!("Received OnString event for string {:?}", response);
Ok(())
}
具体方法是从客户端对象获取事件流,然后等待从中获取单个事件。
运行客户端
为了让客户端和服务器能够使用 Echo
协议进行通信,组件框架必须将 fuchsia.examples.Echo
capability 从服务器路由到客户端。在本教程中,我们提供了一个 realm 组件来声明适当的 capability 和路由。
将 build 配置为包含包含 echo 领域、服务器和客户端的提供的软件包:
fx set core.x64 --with //examples/fidl/rust:echo-rust-client
构建 Fuchsia 映像:
fx build
运行
echo_realm
组件。这会创建客户端和服务器组件实例并路由功能:ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-rust-client#meta/echo_realm.cm
启动
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