协议句柄是一种众所周知的对象,用于提供可使用组件命名空间发现的 FIDL 协议实现。组件框架有助于在组件之间发现协议。功能路由描述了哪个组件应充当任何给定客户端的提供程序。确定正确的组件后,组件管理器会使用每个组件的命名空间中找到的句柄在组件之间发起连接。
请参考以下 fuchsia.example.Foo 协议示例:
 
该图展示了执行连接所涉及的主要元素:
- 提供程序组件在清单的 
capabilities部分中静态声明该协议。这样,组件框架就可以执行 capability 路由。 - 客户端组件在清单的 
use部分中静态请求该协议。如果 capability 路由成功,这会在客户端的命名空间中创建/svc/fuchsia.example.Foo协议条目。 - 提供程序代码会在运行时发布实现。这会在提供商的传出目录的 
/svc/fuchsia.example.Foo中创建一个协议条目。 - 客户端代码会在运行时连接到协议句柄。这会打开与提供程序组件中运行的实现的 FIDL 连接。
 
发布协议实现
实现 FIDL 协议的组件会在其组件清单中声明并公开该协议作为 capability。这样,组件框架便可将 capability 从此组件路由到请求该 capability 的拓扑中的其他组件。
{
    // ...
    capabilities: [
        { protocol: "fuchsia.example.Foo" },
    ],
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}
Capability 路由描述了协议的访问权限,但不会建立连接所需的端点。组件必须使用 fuchsia.io 协议在传出目录中将实现发布为 /svc/ 句柄。生成的 FIDL 绑定会封装此句柄,并让提供程序能够连接请求句柄以开始接收 FIDL 消息。
Rust
let mut service_fs = ServiceFs::new_local();
// Serve the protocol
service_fs.dir("svc").add_fidl_service(PROTOCOL_NAME);
service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
C++
// Serve the protocol
FooImplementation instance;
fidl::Binding<fuchsia::example::Foo> binding(&instance);
instance.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::example::Foo> handler =
    [&](fidl::InterfaceRequest<fuchsia::example::Foo> request) {
      binding.Bind(std::move(request));
    };
context->outgoing()->AddPublicService(std::move(handler));
连接到协议实现
客户端组件会在其组件清单中将该协议声明为必需功能。这样,组件框架便可以确定组件是否有权访问协议实现。如果存在有效路由,则组件的命名空间中包含相应的 /svc/ 句柄。
{
    // ...
    use: [
        { protocol: "fuchsia.example.Foo" },
    ],
}
客户端组件使用 fuchsia.io 协议与协议实现建立连接并打开通道。生成的 FIDL 绑定会封装此通道,并让客户端能够开始向提供方发送消息。
Rust
// Connect to FIDL protocol
let protocol = connect_to_protocol::<FooMarker>().expect("error connecting to echo");
C++
// Connect to FIDL protocol
fuchsia::example::FooSyncPtr proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(proxy.NewRequest());
练习:回声服务器和客户端
在本部分中,您将使用为 fidl.examples.routing.echo 生成的 FIDL 绑定在 Rust 中实现客户端和服务器组件。
启动模拟器
如果您还没有运行的实例,请启动模拟器:
启动新的模拟器实例:
ffx emu start --headless启动完成后,模拟器会输出以下消息并返回:
Logging to "$HOME/.local/share/Fuchsia/ffx/emu/instances/fuchsia-emulator/emulator.log" Waiting for Fuchsia to start (up to 60 seconds)........ Emulator is ready.启动软件包服务器,以便模拟器加载软件包:
fx serve
创建服务器组件
首先,创建一个新的组件项目来实现回声服务器。此组件将提供 Echo 协议并处理传入请求。
在 //vendor/fuchsia-codelab 目录中为名为 echo-server 的新组件创建项目框架:
mkdir -p vendor/fuchsia-codelab/echo-server在新项目目录中创建以下文件和目录结构:
Rust
//vendor/fuchsia-codelab/echo-server
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- src
                            |- main.rs
C++
//vendor/fuchsia-codelab/echo-server
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- main.cc
将以下 build 规则添加到 BUILD.gn 文件以构建和打包服务器组件:
Rust
echo-server/BUILD.gn:
import("//build/components.gni")
import("//build/rust/rustc_binary.gni")
rustc_binary("bin") {
  output_name = "echo-server"
  edition = "2021"
  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_rust",
    "//src/lib/diagnostics/inspect/runtime/rust",
    "//src/lib/diagnostics/inspect/rust",
    "//src/lib/fuchsia",
    "//src/lib/fuchsia-component",
    "//third_party/rust_crates:anyhow",
    "//third_party/rust_crates:futures",
  ]
  sources = [ "src/main.rs" ]
}
# Unpackaged component "#meta/echo_server.cm"
fuchsia_component("echo_server_cmp") {
  component_name = "echo_server"
  manifest = "meta/echo_server.cml"
  deps = [ ":bin" ]
}
fuchsia_package("echo-server") {
  package_name = "echo-server"
  deps = [ ":component" ]
}
C++
echo-server/BUILD.gn:
import("//build/components.gni")
executable("bin") {
  output_name = "echo-server"
  sources = [ "main.cc" ]
  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_cpp",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//sdk/lib/inspect/component/cpp",
    "//sdk/lib/sys/cpp",
    "//sdk/lib/syslog/cpp",
  ]
}
# Unpackaged component "#meta/echo_server.cm"
fuchsia_component("echo_server_cmp") {
  component_name = "echo_server"
  manifest = "meta/echo_server.cml"
  deps = [ ":bin" ]
}
fuchsia_package("echo-server") {
  package_name = "echo-server"
  deps = [ ":component" ]
}
将 Echo 协议声明为服务器组件提供的 capability,并将其公开以供父级 realm 使用:
Rust
echo-server/meta/echo_server.cml:
{
    include: [
        "inspect/client.shard.cml",
        "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/echo-server",
    },
    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
    expose: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "self",
        },
    ],
}
C++
echo-server/meta/echo_server.cml:
{
    include: [
        "inspect/client.shard.cml",
        "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/echo-server",
    },
    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
    expose: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "self",
        },
    ],
}
实现服务器
打开主源文件,并将导入语句替换为以下代码:
Rust
echo-server/src/main.rs:
use anyhow::Context;
use fidl_fidl_examples_routing_echo::{EchoRequest, EchoRequestStream};
use fuchsia_component::server::ServiceFs;
use fuchsia_inspect::component;
use fuchsia_inspect::health::Reporter;
use futures::prelude::*;
C++
echo-server/main.cc:
#include <fidl/fidl.examples.routing.echo/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/inspect/component/cpp/component.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
将以下代码添加到 main() 以提供 Echo 协议:
Rust
echo-server/src/main.rs:
// Wrap protocol requests being served.
enum IncomingRequest {
    Echo(EchoRequestStream),
}
#[fuchsia::main(logging = false)]
async fn main() -> Result<(), anyhow::Error> {
    let mut service_fs = ServiceFs::new_local();
    // Initialize inspect
    component::health().set_starting_up();
    let _inspect_server_task = inspect_runtime::publish(
        component::inspector(),
        inspect_runtime::PublishOptions::default(),
    );
    // Serve the Echo protocol
    service_fs.dir("svc").add_fidl_service(IncomingRequest::Echo);
    service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
    // Component is serving and ready to handle incoming requests
    component::health().set_ok();
    // Attach request handler for incoming requests
    service_fs
        .for_each_concurrent(None, |request: IncomingRequest| async move {
            match request {
                IncomingRequest::Echo(stream) => handle_echo_request(stream).await,
            }
        })
        .await;
    Ok(())
}
此代码会执行以下步骤来提供 Echo 协议:
- 初始化 
ServiceFs,并在传出目录的/svc/fidl.examples.routing.echo.Echo下添加条目。 - 提供目录并开始监听传入连接。
 - 将 
handle_echo_request()函数附加为任何匹配的Echo请求的请求处理脚本。 
C++
echo-server/main.cc:
int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();
  // Initialize inspect
  inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
  inspector.Health().StartingUp();
  component::OutgoingDirectory outgoing_directory = component::OutgoingDirectory(dispatcher);
  zx::result result = outgoing_directory.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }
  result = outgoing_directory.AddUnmanagedProtocol<fidl_examples_routing_echo::Echo>(
      [dispatcher](fidl::ServerEnd<fidl_examples_routing_echo::Echo> server_end) {
        fidl::BindServer(dispatcher, std::move(server_end), std::make_unique<EchoImplementation>());
      });
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to add Echo protocol: " << result.status_string();
    return -1;
  }
  // Component is serving and ready to handle incoming requests
  inspector.Health().Ok();
  return loop.Run();
}
此代码会执行以下步骤来提供 Echo 协议:
- 初始化 
ComponentContext,并在传出目录的/svc/fidl.examples.routing.echo.Echo下添加条目。 - 提供目录并开始监听传入连接。
 - 将 
EchoImplementation实例附加为任何匹配的Echo请求的请求处理程序。 
添加以下代码以实现协议请求处理脚本:
Rust
echo-server/src/main.rs:
// Handler for incoming service requests
async fn handle_echo_request(mut stream: EchoRequestStream) {
    while let Some(event) = stream.try_next().await.expect("failed to serve echo service") {
        let EchoRequest::EchoString { value, responder } = event;
        responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
    }
}
EchoRequestStream 中的每个请求都按方法名称 (EchoString) 进行类型化,并包含用于发回返回值的响应方接口。
C++
echo-server/main.cc:
// Handler for incoming service requests
class EchoImplementation : public fidl::Server<fidl_examples_routing_echo::Echo> {
 public:
  void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
    completer.Reply({{.response = request.value()}});
  }
};
每个 Echo 协议方法都有一个对应的替换函数 (EchoString()),并包含用于发回返回值的回调接口。
此实现只是将请求中的相同字符串值“回传”到响应载荷中。
创建客户端组件
再创建一个新的组件项目来实现 echo 客户端。此组件将连接到协议实现并发送请求。
在 //vendor/fuchsia-codelab 目录中为名为 echo-client 的新组件创建项目框架:
mkdir -p vendor/fuchsia-codelab/echo-client在新项目目录中创建以下文件和目录结构:
Rust
//vendor/fuchsia-codelab/echo-client
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- src
                            |- main.rs
C++
//vendor/fuchsia-codelab/echo-client
                        |- BUILD.gn
                        |- meta
                        |   |- echo.cml
                        |
                        |- main.cc
将以下 build 规则添加到 BUILD.gn 文件,以构建和打包客户端组件:
Rust
echo-client/BUILD.gn:
import("//build/components.gni")
import("//build/rust/rustc_binary.gni")
rustc_binary("bin") {
  output_name = "echo-client"
  edition = "2021"
  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_rust",
    "//src/lib/fuchsia",
    "//src/lib/fuchsia-component",
    "//third_party/rust_crates:anyhow",
    "//third_party/rust_crates:log",
  ]
  sources = [ "src/main.rs" ]
}
# Unpackaged component "#meta/echo_client.cm"
fuchsia_component("echo_client_cmp") {
  component_name = "echo_client"
  manifest = "meta/echo_client.cml"
  deps = [ ":bin" ]
}
fuchsia_package("echo-client") {
  package_name = "echo-client"
  deps = [ ":component" ]
}
C++
echo-client/BUILD.gn:
import("//build/components.gni")
executable("bin") {
  output_name = "echo-client"
  sources = [ "main.cc" ]
  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_cpp",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//sdk/lib/component/incoming/cpp",
    "//sdk/lib/sys/cpp",
    "//sdk/lib/syslog/cpp",
  ]
}
# Unpackaged component "#meta/echo_client.cm"
fuchsia_component("echo_client_cmp") {
  component_name = "echo_client"
  manifest = "meta/echo_client.cml"
  deps = [ ":bin" ]
}
fuchsia_package("echo-client") {
  package_name = "echo-client"
  deps = [ ":component" ]
}
配置客户端的组件清单以请求服务器公开的 fidl.examples.routing.echo.Echo capability:
Rust
echo-client/meta/echo_client.cml:
{
    include: [
        // Enable logging on stdout
        "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/echo-client",
        // Program arguments
        args: [ "Hello Fuchsia!" ],
    },
    // Capabilities used by this component.
    use: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
}
C++
echo-client/meta/echo_client.cml:
{
    include: [
        // Enable logging.
        "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/echo-client",
        // Program arguments
        args: [ "Hello Fuchsia!" ],
    },
    // Capabilities used by this component.
    use: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
}
实现客户端
与 echo-args 类似,客户端将程序参数作为消息传递给服务器。将以下程序参数添加到 echo_client.cml:
Rust
echo-client/meta/echo_client.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/echo-client",
    // Program arguments
    args: [ "Hello Fuchsia!" ],
},
C++
echo-client/meta/echo_client.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/echo-client",
    // Program arguments
    args: [ "Hello Fuchsia!" ],
},
打开主源文件,并将导入语句替换为以下代码:
Rust
echo-client/src/main.rs:
use fidl_fidl_examples_routing_echo::EchoMarker;
use fuchsia_component::client::connect_to_protocol;
C++
echo-client/main.cc:
#include <fidl/fidl.examples.routing.echo/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fidl/cpp/string.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <cstdlib>
#include <iostream>
#include <string>
将以下代码添加到 main() 以连接到 Echo 协议并发送请求:
Rust
echo-client/src/main.rs:
#[fuchsia::main]
async fn main() -> Result<(), anyhow::Error> {
    // Parse arguments, removing binary name
    let mut args: Vec<String> = std::env::args().collect();
    args.remove(0);
    // Connect to FIDL protocol
    let echo = connect_to_protocol::<EchoMarker>().expect("error connecting to echo");
    // Send messages over FIDL interface
    for message in args {
        let out = echo.echo_string(Some(&message)).await.expect("echo_string failed");
        log::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
    }
    Ok(())
}
EchoMarker 提供封装容器,用于按名称连接到公开的 capability,并返回对打开的 EchoProxy 接口的句柄。此代理包含 echo_string() FIDL 协议方法。
C++
echo-client/main.cc:
int main(int argc, const char* argv[], char* envp[]) {
  // Set tags for logging.
  fuchsia_logging::LogSettingsBuilder builder;
  builder.WithTags({"echo_client"}).BuildAndInitialize();
  // Connect to FIDL protocol
  zx::result client_end = component::Connect<fidl_examples_routing_echo::Echo>();
  if (client_end.is_error()) {
    FX_LOGS(ERROR) << "Failed to connect to Echo protocol: " << client_end.status_string();
    return EXIT_FAILURE;
  }
  fidl::SyncClient client(std::move(client_end.value()));
  // Send messages over FIDL interface for each argument
  fidl::StringPtr response = nullptr;
  for (int i = 1; i < argc; i++) {
    fidl::Result response = client->EchoString({argv[i]});
    if (response.is_error()) {
      FX_LOGS(ERROR) << "echo_string failed: " << response.error_value();
      return EXIT_FAILURE;
    }
    if (!response->response().has_value()) {
      FX_LOGS(ERROR) << "echo_string got empty result";
      return EXIT_FAILURE;
    }
    const std::string& response_value = response->response().value();
    FX_LOG_KV(INFO, "Server response", FX_KV("response", response_value));
  }
  return 0;
}
EchoSyncPtr 提供一个封装容器,用于按名称连接到公开的 capability,并返回对打开的代理接口的句柄。此代理包含 EchoString() FIDL 协议方法。
集成组件
服务器提供的功能必须通过组件框架路由到客户端。为此,您将实现一个 realm 组件来充当父级并管理 capability 路由。
为 Realm 产品定义创建一个新的项目目录:
mkdir -p vendor/fuchsia-codelab/echo-realm在新项目目录中创建以下文件和目录结构:
//vendor/fuchsia-codelab/echo-realm
                        |- BUILD.gn
                        |- meta
                        |   |- echo_realm.cml
创建一个包含以下内容的新组件清单文件 meta/echo_realm.cml:
echo-realm/meta/echo_realm.cml:
{
    // Two children: a server and client.
    children: [
        {
            name: "echo_server",
            url: "#meta/echo_server.cm",
        },
        {
            name: "echo_client",
            url: "#meta/echo_client.cm",
        },
    ],
    offer: [
        // Route Echo protocol from server to client.
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "#echo_server",
            to: "#echo_client",
        },
        // Route diagnostics protocols to both children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#echo_client",
                "#echo_server",
            ],
        },
    ],
}
这会创建一个组件王国,其中服务器和客户端作为子组件,并将 fidl.examples.routing.echo.Echo 协议功能路由到客户端。
添加 BUILD.gn 文件以为 Realm 组件创建构建目标:
echo-realm/BUILD.gn:
import("//build/components.gni")
fuchsia_component("echo_realm") {
  manifest = "meta/echo_realm.cml"
}
fuchsia_package("echo-realm") {
  deps = [
    ":echo_realm",
    "//vendor/fuchsia-codelab/echo-server:component",
    "//vendor/fuchsia-codelab/echo-client:component",
  ]
}
更新 build 配置以添加新组件:
fx set workstation_eng.x64 \
    --with //vendor/fuchsia-codelab/echo-server \
    --with //vendor/fuchsia-codelab/echo-client \
    --with //vendor/fuchsia-codelab/echo-realm再次运行 fx build 以构建组件:
fx build将组件添加到拓扑中
您将组件添加到 ffx-laboratory,这是一个受限集合,用于在产品的核心领域内进行开发。借助集合,您可以在运行时动态创建和销毁组件。
通过将 echo-realm 组件网址和 ffx-laboratory 中的适当标识符传递给 ffx component create 来创建组件实例:
ffx component create /core/ffx-laboratory:echo-realm \
    fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm然后,使用 ffx component resolve 解析 echo-realm 组件:
ffx component resolve /core/ffx-laboratory:echo-realm验证服务器和客户端的实例是否也使用 ffx component show 创建为子组件:
ffx component show echo               Moniker: /core/ffx-laboratory:echo-realm/echo_client
                   URL: #meta/echo_client.cm
                  Type: CML static component
       Component State: Unresolved
       Execution State: Stopped
               Moniker: /core/ffx-laboratory:echo-realm/echo_server
                   URL: #meta/echo_server.cm
                  Type: CML static component
       Component State: Unresolved
       Execution State: Stopped
               Moniker: /core/ffx-laboratory:echo-realm
                   URL: fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm
                  Type: CML dynamic component
       Component State: Resolved
       Execution State: Stopped
           Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
验证组件互动
使用 ffx component start 启动现有客户端组件实例:
ffx component start /core/ffx-laboratory:echo-realm/echo_client打开另一个终端窗口,并验证客户端组件中的日志输出:
ffx log --filter echo您应该会在设备日志中看到以下输出:
[echo_client][I] Server response: Hello, Fuchsia!
客户端与 fidl.examples.routing.echo.Echo capability 建立连接后,服务器组件便会启动并继续运行,以处理其他 FIDL 请求。
使用 ffx component show 查看在组件实例树中运行的 Echo 服务器:
ffx component show echo_server               Moniker: /core/ffx-laboratory:echo-realm/echo_server
                   URL: #meta/echo_server.cm
                  Type: CML static component
       Component State: Resolved
 Incoming Capabilities: fuchsia.logger.LogSink
  Exposed Capabilities: diagnostics
                        fidl.examples.routing.echo.Echo
       Execution State: Running
                Job ID: 474691
            Process ID: 474712
           Running for: 2026280474361 ticks
           Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
 Outgoing Capabilities: diagnostics
                        fidl.examples.routing.echo.Echo
销毁实例
使用以下命令清理 echo-realm 实例:
ffx component destroy /core/ffx-laboratory:echo-realm