前提条件
本教程假定您熟悉列出 FIDL Rust 的步骤 将某个库的绑定作为 GN 中的依赖项,并将绑定导入 Rust 代码,在 Rust FIDL crate 教程中进行了介绍。
概览
本教程介绍了如何实现 FIDL 协议
(fuchsia.examples.Echo
) 并在 Fuchsia 上运行它。此协议有一种方法
:
EchoString
是带有响应的方法。SendString
是没有响应的方法。OnString
是一个事件。
@discoverable
closed protocol Echo {
strict EchoString(struct {
value string:MAX_STRING_LENGTH;
}) -> (struct {
response string:MAX_STRING_LENGTH;
});
strict SendString(struct {
value string:MAX_STRING_LENGTH;
});
strict -> OnString(struct {
response string:MAX_STRING_LENGTH;
});
};
如需详细了解 FIDL 方法和消息传递模型,请参阅 FIDL 概念页面。
本文档介绍了如何完成以下任务:
- 实现 FIDL 协议。
- 在 Fuchsia 上构建并运行软件包。
- 提供 FIDL 协议。
本教程首先创建一个投放到 Fuchsia 设备的组件 并运行然后,逐步添加功能,让服务器正常运行。
如果要自行编写代码,请删除以下目录:
rm -r examples/fidl/rust/server/*
创建组件
要创建组件,请执行以下操作:
将
main()
函数添加到examples/fidl/rust/server/src/main.rs
:fn main() { println!("Hello, world!"); }
在
examples/fidl/rust/server/BUILD.gn
中声明服务器的目标:import("//build/rust/rustc_binary.gni") # Declare an executable for the server. This produces a binary with the # specified output name that can run on Fuchsia. rustc_binary("bin") { output_name = "fidl_echo_rust_server" edition = "2021" sources = [ "src/main.rs" ] } # Declare a component for the server, which consists of the manifest and the # binary that the component will run. fuchsia_component("echo-server") { component_name = "echo_server" manifest = "meta/server.cml" deps = [ ":bin" ] } # Declare a package that contains a single component, our server. fuchsia_package("echo-rust-server") { deps = [ ":echo-server" ] }
要启动并运行服务器组件,需要设置 定义:
- 服务器构建为在 Fuchsia 上运行的原始可执行文件。
- 一种组件,设置为仅运行服务器可执行文件, 该声明使用组件的清单文件进行描述
- 然后,该组件被放入软件包中,软件包是软件单元 在 Fuchsia 上的应用分发。在本例中, 包只包含 单个组件。
如需详细了解软件包、组件以及如何构建它们,请参阅 构建组件页面。
在
examples/fidl/rust/server/meta/server.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_server", }, // Capabilities provided by this component. capabilities: [ { protocol: "fuchsia.examples.Echo" }, ], expose: [ { protocol: "fuchsia.examples.Echo", from: "self", }, ], }
将该服务器添加到您的构建配置中:
fx set core.x64 --with //examples/fidl/rust/server:echo-rust-server
构建 Fuchsia 映像:
fx build
实现服务器
首先,您将实现 Echo 协议的行为。在 Rust 中,
可处理协议关联请求流类型的代码,在本例中为
EchoRequestStream
。此类型是 Echo 请求流,也就是说,它会实现
futures::Stream<Item = Result<EchoRequest, fidl::Error>>
。
您将实现 run_echo_server()
来处理请求流,
后者是一个用于处理传入服务请求的异步函数。
它会返回在客户端通道关闭后完成的 Future。
添加依赖项
导入所需的依赖项:
// we'll use anyhow to propagate errors that occur when handling the request stream use anyhow::{Context as _, Error}; // the server will need to handle an EchoRequestStream use fidl_fuchsia_examples::{EchoRequest, EchoRequestStream}; // import the futures prelude, which includes things like the Future and Stream traits use futures::prelude::*;
将它们作为 build 依赖项添加到
rustc_binary
目标中。deps 字段应如下所示:deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_rust", "//third_party/rust_crates:anyhow", "//third_party/rust_crates:futures", ]
定义 run_echo_server
:
// An implementation of the Echo stream, which handles a stream of EchoRequests
async fn run_echo_server(stream: EchoRequestStream) -> Result<(), Error> {
stream
.map(|result| result.context("failed request"))
.try_for_each(|request| async move {
match request {
// Handle each EchoString request by responding with the request
// value
EchoRequest::EchoString { value, responder } => {
println!("Received EchoString request for string {:?}", value);
responder.send(&value).context("error sending response")?;
println!("Response sent successfully");
}
// Handle each SendString request by sending a single OnString
// event with the request value
EchoRequest::SendString { value, control_handle } => {
println!("Received SendString request for string {:?}", value);
control_handle.send_on_string(&value).context("error sending event")?;
println!("Event sent successfully");
}
}
Ok(())
})
.await
}
该实现包含以下元素:
该代码会将请求流中的
fidl:Error
转换为anyhow::Error
,方法是将 对每个结果使用.context()
方法:// An implementation of the Echo stream, which handles a stream of EchoRequests async fn run_echo_server(stream: EchoRequestStream) -> Result<(), Error> { stream .map(|result| result.context("failed request")) .try_for_each(|request| async move { match request { // Handle each EchoString request by responding with the request // value EchoRequest::EchoString { value, responder } => { println!("Received EchoString request for string {:?}", value); responder.send(&value).context("error sending response")?; println!("Response sent successfully"); } // Handle each SendString request by sending a single OnString // event with the request value EchoRequest::SendString { value, control_handle } => { println!("Received SendString request for string {:?}", value); control_handle.send_on_string(&value).context("error sending event")?; println!("Event sent successfully"); } } Ok(()) }) .await }
在此阶段,
Result<EchoRequest, fidl::Error>
流会变为Result<EchoRequest, anyhow::Error>
。然后,该函数会对生成的流调用 try_for_each,从而返回 。此方法解封数据流中的
Result
- 任何失败都会导致未来 立即返回包含该错误的错误消息,任何成功的内容都会传递到 。同样,如果闭包的返回值解析为失败,则生成的 Future 会立即返回并显示该错误:// An implementation of the Echo stream, which handles a stream of EchoRequests async fn run_echo_server(stream: EchoRequestStream) -> Result<(), Error> { stream .map(|result| result.context("failed request")) .try_for_each(|request| async move { match request { // Handle each EchoString request by responding with the request // value EchoRequest::EchoString { value, responder } => { println!("Received EchoString request for string {:?}", value); responder.send(&value).context("error sending response")?; println!("Response sent successfully"); } // Handle each SendString request by sending a single OnString // event with the request value EchoRequest::SendString { value, control_handle } => { println!("Received SendString request for string {:?}", value); control_handle.send_on_string(&value).context("error sending event")?; println!("Event sent successfully"); } } Ok(()) }) .await }
闭包的内容通过将传入的
EchoRequest
与 确定请求类型:// An implementation of the Echo stream, which handles a stream of EchoRequests async fn run_echo_server(stream: EchoRequestStream) -> Result<(), Error> { stream .map(|result| result.context("failed request")) .try_for_each(|request| async move { match request { // Handle each EchoString request by responding with the request // value EchoRequest::EchoString { value, responder } => { println!("Received EchoString request for string {:?}", value); responder.send(&value).context("error sending response")?; println!("Response sent successfully"); } // Handle each SendString request by sending a single OnString // event with the request value EchoRequest::SendString { value, control_handle } => { println!("Received SendString request for string {:?}", value); control_handle.send_on_string(&value).context("error sending event")?; println!("Event sent successfully"); } } Ok(()) }) .await }
此实现通过回显输入来处理
EchoString
请求, 通过发送OnString
事件实现SendString
请求。由于SendString
是一个火并且忽略 方法,请求枚举变体附带一个控制句柄,可用于 与服务器通信在这两种情况下,将消息发送回客户端的错误都是通过添加上下文来传播 使用
?
运算符。如果成功到达关闭的终点,则返回Ok(())
。最后,服务器函数
await
从try_for_each
返回至完成的 Future, 将对每个传入请求调用闭包,并在所有请求均已处理完毕时返回 或遇到任何错误。
您可以通过运行以下命令来验证实现是否正确:
fx build
提供协议
现在,您已经定义了用于处理传入请求的代码,接下来您需要监听传入 Echo 服务器的连接。方法是询问 组件管理器,以便为其他组件提供 Echo 协议。通过 然后,配套管理器会将 echo 协议的所有请求路由到我们的服务器。
要执行这些请求,组件管理器需要协议的名称 以及在有传入请求时应调用的处理程序。 连接到与指定名称匹配的协议。
添加依赖项
导入所需的依赖项:
// Import the Fuchsia async runtime in order to run the async main function use fuchsia_async as fasync; // ServiceFs is a filesystem used to connect clients to the Echo service use fuchsia_component::server::ServiceFs;
将它们作为 build 依赖项添加到
rustc_binary
目标中。完整目标如下所示:rustc_binary("bin") { name = "fidl_echo_rust_server" edition = "2021" 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", ] sources = [ "src/main.rs" ] }
定义 main
函数
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Initialize the outgoing services provided by this component
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Serve the outgoing services
fs.take_and_serve_directory_handle()?;
// Listen for incoming requests to connect to Echo, and call run_echo_server
// on each one
println!("Listening for incoming connections...");
const MAX_CONCURRENT: usize = 10_000;
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Echo(stream)| {
run_echo_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
主要函数是异步的,因为它涉及监听到
回声服务器。fuchsia::main
属性指示紫红色异步运行时运行
main
未来在单个线程上完成。
main
也会返回 Result<(), Error>
。如果从 main
返回 Error
因为其中有 ?
行,那么系统会输出 Debug
错误,
程序会返回一个表示失败的状态代码。
初始化 ServiceFs
获取 ServiceFs
的实例,该实例表示包含各种服务的文件系统。
由于服务器将在单线程中运行,因此请使用
ServiceFs::new_local()
,而不是 ServiceFs::new()
(后者支持多线程)。
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Initialize the outgoing services provided by this component
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Serve the outgoing services
fs.take_and_serve_directory_handle()?;
// Listen for incoming requests to connect to Echo, and call run_echo_server
// on each one
println!("Listening for incoming connections...");
const MAX_CONCURRENT: usize = 10_000;
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Echo(stream)| {
run_echo_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
添加 Echo FIDL 服务
请求组件管理器公开 Echo FIDL 服务。此部分包含两个部分 函数调用:
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Initialize the outgoing services provided by this component
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Serve the outgoing services
fs.take_and_serve_directory_handle()?;
// Listen for incoming requests to connect to Echo, and call run_echo_server
// on each one
println!("Listening for incoming connections...");
const MAX_CONCURRENT: usize = 10_000;
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Echo(stream)| {
run_echo_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
组件管理器必须知道如何处理传入的连接请求。这是指定的 方法是传入一个接受
fidl::endpoints::RequestStream
的闭包,并返回一些新的 值。例如,传入|stream: EchoRequestStream| stream
的闭包会 是完全有效的。常见模式 定义服务器提供的可能服务的枚举,在此示例中:enum IncomingService { // Host a service protocol. Echo(EchoRequestStream), // ... more services here }
然后传递枚举变体“structor”关闭。当有多项服务时 这会产生常见的返回值类型(
IncomingService
枚举)。回归 在以下情况下,所有add_fidl_service
闭包的值都将成为ServiceFs
流中的元素: 监听传入连接。组件管理器还必须知道该服务将在何处提供。 由于这是一项传出服务(即提供给其他组件的服务), 该服务必须在
/svc
目录内添加路径。add_fidl_service
会获取 通过采用SERVICE_NAME
隐式创建路径 与闭包输入参数相关联。 在本例中,闭包实参 (IncomingService::Echo
) 有一个类型为EchoRequestStream
,其关联SERVICE_NAME
为"fuchsia.examples.Echo"
。所以这个 调用会在/svc/fuchsia.examples.Echo
添加一个条目,而客户需要搜索 名为"fuchsia.examples.Echo"
的服务连接到此服务器。
提供传出目录
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Initialize the outgoing services provided by this component
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Serve the outgoing services
fs.take_and_serve_directory_handle()?;
// Listen for incoming requests to connect to Echo, and call run_echo_server
// on each one
println!("Listening for incoming connections...");
const MAX_CONCURRENT: usize = 10_000;
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Echo(stream)| {
run_echo_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
此调用会将 ServiceFs
绑定到组件的 DirectoryRequest
启动句柄;
监听传入的连接请求。
请注意,由于这会从进程的句柄表中删除句柄,
每个进程只能调用此函数一次。如果您希望提供
ServiceFs
到其他频道,则可以使用 serve_connection
函数。
此过程在 开放协议的生命周期。
监听传入连接
运行 ServiceFs
以完成操作,以监听传入连接:
#[fuchsia::main]
async fn main() -> Result<(), Error> {
// Initialize the outgoing services provided by this component
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Serve the outgoing services
fs.take_and_serve_directory_handle()?;
// Listen for incoming requests to connect to Echo, and call run_echo_server
// on each one
println!("Listening for incoming connections...");
const MAX_CONCURRENT: usize = 10_000;
fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Echo(stream)| {
run_echo_server(stream).unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
这会运行 ServiceFs
Future,最多可处理 10,000 个传入
同时处理多个请求传递到此调用的闭包是用于传入请求的处理程序 -
ServiceFs
将首先将传入的连接与提供给 add_fidl_service
的闭包进行匹配,
然后对结果(即 IncomingService
)调用处理程序。处理程序将
IncomingService
,然后对内部请求流调用 run_echo_server
来处理传入
回声请求。
此处处理的请求有两种。由
ServiceFs
包含连接到 Echo 服务器的请求(也就是说,每个客户端都会将此类型设为
一次请求),而由服务器处理的请求
run_echo_server
是基于 Echo 协议的请求(即每个客户端可以发出任意数量的
向服务器发出 EchoString
或 SendString
请求)。许多客户端可以请求连接到
同时回声服务器,以便同时处理这些请求流。不过,
针对单个客户端的请求是按顺序进行的,因此处理请求并不会带来任何好处
同时进行。
测试服务器
重建:
fx build
然后运行服务器组件:
ffx component run /core/ffx-laboratory:echo_server fuchsia-pkg://fuchsia.com/echo-rust-server#meta/echo_server.cm
注意:组件是使用其 组件网址 , 该值取决于 `fuchsia-pkg://` 架构。
您应该会在设备日志 (ffx log
) 中看到类似于以下内容的输出:
[ffx-laboratory:echo_server][][I] Listening for incoming connections...
服务器现在正在运行并等待传入请求。
下一步是编写一个发送 Echo
协议请求的客户端。
目前,您可以直接终止服务器组件:
ffx component destroy /core/ffx-laboratory:echo_server