连接组件

协议句柄是众所周知的对象,可提供 可使用组件命名空间检测到的 FIDL 协议。组件 框架有助于在 Google Cloud 和 Google Cloud 组件 功能 功能路由描述了哪个组件应充当 。找出合适的组件后 组件管理器 使用在每个组件中找到的句柄在组件之间 该组件的命名空间。

请参考以下示例,了解 fuchsia.example.Foo 协议:

展示连接组件如何成为功能组合的示意图
提供路由和协议服务组件必须提供
向其他组件提供的协议

下图突出显示了执行连接所涉及的主要元素:

  1. 提供程序组件以静态方式声明 capabilities 部分。这样,组件框架就可以 执行功能路由。
  2. 客户端组件在 use 部分中以静态方式请求协议 。这将创建 /svc/fuchsia.example.Foo 协议条目 (如果功能路由成功)。
  3. 提供程序代码会在运行时发布实现。这会创建 提供商外拨电话中 /svc/fuchsia.example.Foo 处的协议条目 目录。
  4. 客户端代码在运行时会连接到协议句柄。这会打开一个 与提供程序组件中运行的实现的 FIDL 连接。

发布协议实现

实现 FIDL 协议的组件会声明公开 作为一项功能添加到其组件清单中。这样,该组件 以便执行从该组件到 请求该功能的拓扑。

{
    // ...
    capabilities: [
        { protocol: "fuchsia.example.Foo" },
    ],
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}

功能路由描述的是协议的访问权限 没有为连接建立必要的端点。组件必须发布 使用/svc/ fuchsia.io 协议。通过 生成的 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" },
    ],
}
<ph type="x-smartling-placeholder">

客户端组件使用 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());

练习:Echo 服务器和客户端

在本部分中,您将使用生成的 FIDL 绑定, fidl.examples.routing.echo,用于在 Rust 中实现客户端和服务器组件。

启动模拟器

如果您尚未运行实例,请启动模拟器:

  1. 启动新的模拟器实例:

    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.
    
  2. 启动软件包服务器,使模拟器能够加载软件包:

    fx serve
    

创建服务器组件

首先,创建一个新的组件项目来实现 echo 服务器。这个 组件将提供 Echo 协议并处理传入请求。

echo-server //vendor/fuchsia-codelab 目录:

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.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_hlcpp",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//sdk/lib/inspect/component/cpp",
    "//sdk/lib/sys/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 协议声明为服务器组件提供的功能。 并将其公开,以供父域使用:

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",
        },
    ],
}

实现服务器

打开主源文件,并将 import 语句替换为 以下代码:

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::*;
<ph type="x-smartling-placeholder">

C++

echo-server/main.cc

#include <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>

将以下代码添加到 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 协议:

  1. 初始化 ServiceFs 并在下面添加一个条目 传出目录中的 /svc/fidl.examples.routing.echo.Echo
  2. 提供该目录并开始监听传入的连接。
  3. handle_echo_request() 函数作为任何 匹配 Echo 个请求。

C++

echo-server/main.cc

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();

  // Initialize inspect
  inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
  inspector.Health().StartingUp();

  // Serve the Echo protocol
  EchoImplementation echo_instance;
  fidl::Binding<fidl::examples::routing::echo::Echo> binding(&echo_instance);
  echo_instance.event_sender_ = &binding.events();
  fidl::InterfaceRequestHandler<fidl::examples::routing::echo::Echo> handler =
      [&](fidl::InterfaceRequest<fidl::examples::routing::echo::Echo> request) {
        binding.Bind(std::move(request));
      };
  context->outgoing()->AddPublicService(std::move(handler));

  // Component is serving and ready to handle incoming requests
  inspector.Health().Ok();

  return loop.Run();
}

此代码执行以下步骤以提供 Echo 协议:

  1. 初始化 ComponentContext 并在下面添加一个条目 传出目录中的 /svc/fidl.examples.routing.echo.Echo
  2. 提供该目录并开始监听传入的连接。
  3. 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::examples::routing::echo::Echo {
 public:
  void EchoString(fidl::StringPtr value, EchoStringCallback callback) override { callback(value); }
  fidl::examples::routing::echo::Echo_EventSender* event_sender_;
};

每个 Echo 协议方法都有一个对应的替换函数 (EchoString()) 并包含一个回调接口,用于发回返回值。

此实现只是“回显”请求返回的相同字符串值 。

创建客户端组件

再创建一个新的组件项目来实现 echo 客户端。这个 组件将连接到协议实现并发送请求。

echo-client //vendor/fuchsia-codelab 目录:

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.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:tracing",
  ]

  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_hlcpp",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//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 功能:

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!" ],
},

打开主源文件,并将 import 语句替换为以下代码:

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/examples/routing/echo/cpp/fidl.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");
        tracing::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
    }

    Ok(())
}

EchoMarker 提供了一个封装容器,用于通过 并为打开的 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
  fidl::examples::routing::echo::EchoSyncPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  // Send messages over FIDL interface for each argument
  fidl::StringPtr response = nullptr;
  for (int i = 1; i < argc; i++) {
    ZX_ASSERT(echo_proxy->EchoString(argv[i], &response) == ZX_OK);
    if (!response.has_value()) {
      FX_LOG_KV(INFO, "echo_string got empty result");
    } else {
      FX_LOG_KV(INFO, "Server response", FX_KV("response", response->c_str()));
    }
  }

  return 0;
}

EchoSyncPtr 提供了一个封装容器,用于通过 name 并向开放代理接口返回一个句柄。此代理包含 EchoString() FIDL 协议方法。

集成组件

服务器提供的功能必须通过 组件框架为此,您需要实现一个 Realm 组件 作为父项并管理 capability 路由。

为大区产品定义创建一个新的项目目录:

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.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            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功能并继续运行 其他 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