必要條件
本教學課程假設您已熟悉列出 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" ] }
為了讓伺服器元件正常執行,在 Pod 中 定義:
- 專為在 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::*;
並將其新增為
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
的未來傳回至完成, 會在每次收到要求時呼叫 closure,並在所有要求都擷取到 處理事件或發生任何錯誤。
您可以執行下列指令,驗證實作是否正確:
fx build
提供通訊協定
您已經定義處理傳入要求的程式碼,接下來需要監聽 連線至 Echo 伺服器做法是詢問 元件管理員,向其他元件公開 Echo 通訊協定。 接著,Comopent Manager 會將 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
與 closure 輸入引數相關聯。 在這個範例中,閉包引數 (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
未來,最多可處理 10,000 次傳入
同時處理多個要求傳遞至此呼叫的閉包是用於傳入要求的處理常式 -
ServiceFs
會先比對連入與提供給 add_fidl_service
的連入連線,
然後呼叫結果 (即 IncomingService
) 的處理常式。處理常式會擷取
IncomingService
,並在內部要求串流上呼叫 run_echo_server
來處理傳入的要求
回聲要求。
這裡正在處理兩種要求。由
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
注意:系統會使用 元件網址 , 取決於 `fuchsia-pkg://` 配置。
您應該會在裝置記錄 (ffx log
) 中看到類似以下的輸出內容:
[ffx-laboratory:echo_server][][I] Listening for incoming connections...
伺服器現在開始執行,正在等待傳入的要求。
下一個步驟是編寫可傳送 Echo
通訊協定要求的用戶端。
目前,您可以直接終止伺服器元件:
ffx component destroy /core/ffx-laboratory:echo_server