必要條件
本教學課程假設您熟悉將程式庫的 FIDL Rust 繫結列為 GN 中的依附元件,並將繫結匯入 Rust 程式碼,詳情請參閱 Rust FIDL crates 教學課程。
總覽
本教學課程說明如何實作 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/*
建立元件
如何建立元件:
在
examples/fidl/rust/server/src/main.rs
中新增main()
函式: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()
來處理要求串流,這是處理傳入服務要求的非同步函式。它會傳回用戶端管道關閉後完成的未來。
新增依附元件
匯入必要的依附元件:
// 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::*;
將其新增為
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
}
實作程序包含下列元素:
程式碼會在每個結果使用
.context()
方法附加內容,將要求串流中的fidl:Error
轉換為anyhow::Error
:// 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
解除包裝;任何失敗都會導致日後立即傳回該錯誤,而所有成功的內容都會傳遞給閉包。同樣地,如果關閉事件的傳回值解析為失敗,則產生的未來會立即傳回該錯誤:// 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
傳回至完成的未來,這會針對每個傳入要求呼叫關閉功能,並在所有要求都已處理或發生任何錯誤時傳回。
您可以執行下列指令來驗證實作是否正確:
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;
將其新增為
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(())
}
主函式會監聽與 Echo 伺服器的連入連線,因此是非同步的。fuchsia::main
屬性會指示 fuchsia 非同步執行階段執行 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 }
然後傳遞列舉變數「建構函式」做為閉包。如果提供多項服務,這會產生常見的傳回類型 (
IncomingService
列舉)。在監聽傳入連線時,所有add_fidl_service
閉包的傳回值會成為ServiceFs
串流中的元素。元件管理員也必須知道可以使用這項服務的位置。 由於這是外送服務 (例如提供給其他元件的服務),因此該服務必須在
/svc
目錄內新增路徑。add_fidl_service
會透過與封閉輸入引數相關聯的SERVICE_NAME
,以隱含方式取得這個路徑。在這種情況下,閉包引數 (IncomingService::Echo
) 含有EchoRequestStream
類型的輸入引數,該引數與"fuchsia.examples.Echo"
的SERVICE_NAME
相關聯。因此,這個呼叫會在/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
未來,最多可同時處理 10,000 個傳入要求。傳遞至此呼叫的閉包是用於傳入要求的處理常式。ServiceFs
會先比對傳入至 add_fidl_service
的封閉連線,再根據結果呼叫處理常式 (也就是 IncomingService
)。處理常式會接收 IncomingService
,並在內部要求串流上呼叫 run_echo_server
來處理傳入的 Echo 要求。
這裡處理的要求類型分為兩種。由 ServiceFs
處理的要求串流包含連線至 Echo 伺服器的要求 (即每個用戶端會在連線至伺服器時一次發出這類要求),而 run_echo_server
處理的要求串流則是在 Echo 通訊協定上的要求 (也就是說,每個用戶端都可向伺服器發出任意數量的 EchoString
或 SendString
要求)。許多用戶端可以同時要求連線至 Echo 伺服器,因此,系統會同時處理這個要求串流。不過,單一用戶端的所有要求都是按照順序完成,因此在並行處理要求沒有好處。
測試伺服器
重新建構:
fx build
然後執行伺服器元件:
ffx component run /core/ffx-laboratory:echo_server fuchsia-pkg://fuchsia.com/echo-rust-server#meta/echo_server.cm
您應該會在裝置記錄 (ffx log
) 中看到類似以下的輸出內容:
[ffx-laboratory:echo_server][][I] Listening for incoming connections...
伺服器現已執行並等候傳入要求。下一步是編寫傳送 Echo
通訊協定要求的用戶端。目前,您可以直接終止伺服器元件:
ffx component destroy /core/ffx-laboratory:echo_server