{"id":10008279,"date":"2023-09-18T20:30:19","date_gmt":"2023-09-18T12:30:19","guid":{"rendered":"https:\/\/blog.itmax.cn\/?p=10008279"},"modified":"2023-09-18T20:30:19","modified_gmt":"2023-09-18T12:30:19","slug":"%e6%96%87%e7%9b%98rust-tonic-rust-grpc%e5%88%9d%e4%bd%93%e9%aa%8c-%e4%ba%ac%e4%b8%9c%e4%ba%91%e6%8a%80%e6%9c%af%e5%9b%a2%e9%98%9f","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10008279","title":{"rendered":"\u6587\u76d8Rust &#8212; tonic-Rust grpc\u521d\u4f53\u9a8c | \u4eac\u4e1c\u4e91\u6280\u672f\u56e2\u961f"},"content":{"rendered":"<p>gRPC \u662f\u5f00\u53d1\u4e2d\u5e38\u7528\u7684\u5f00\u6e90\u9ad8\u6027\u80fd\u8fdc\u7a0b\u8fc7\u7a0b\u8c03\u7528\uff08RPC\uff09\u6846\u67b6\uff0ctonic \u662f\u57fa\u4e8e HTTP\/2 \u7684 gRPC \u5b9e\u73b0\uff0c\u4e13\u6ce8\u4e8e\u9ad8\u6027\u80fd\u3001\u4e92\u64cd\u4f5c\u6027\u548c\u7075\u6d3b\u6027\u3002\u8be5\u5e93\u7684\u521b\u5efa\u662f\u4e3a\u4e86\u5bf9 async\/await \u63d0\u4f9b\u4e00\u6d41\u7684\u652f\u6301\uff0c\u5e76\u5145\u5f53\u7528 Rust \u7f16\u5199\u7684\u751f\u4ea7\u7cfb\u7edf\u7684\u6838\u5fc3\u6784\u5efa\u5757\u3002\u4eca\u5929\u6211\u4eec\u804a\u804a\u901a\u8fc7\u4f7f\u7528tonic \u8c03\u7528grpc\u7684\u7684\u5177\u4f53\u8fc7\u7a0b\u3002<\/p>\n<h2>\u5de5\u7a0b\u89c4\u5212<\/h2>\n<p>rpc\u7a0b\u5e8f\u4e00\u822c\u5305\u542bserver\u7aef\u548cclient\u7aef\uff0c\u4e3a\u4e86\u65b9\u4fbf\u6211\u4eec\u628a\u4e24\u4e2a\u7a0b\u5e8f\u6253\u5305\u5230\u4e00\u4e2a\u5de5\u7a0b\u91cc\u9762 \u65b0\u5efatonic_sample\u5de5\u7a0b<\/p>\n<pre><code class=\"toml\">cargo new tonic_sample\n\n\n\n<\/code><\/pre>\n<p>Cargo.toml \u5982\u4e0b<\/p>\n<pre><code class=\"toml\">[package]\nname = &quot;tonic_sample&quot;\nversion = &quot;0.1.0&quot;\nedition = &quot;2021&quot;\n\n[[bin]] # Bin to run the gRPC server\nname = &quot;stream-server&quot;\npath = &quot;src\/stream_server.rs&quot;\n\n[[bin]] # Bin to run the gRPC client\nname = &quot;stream-client&quot;\npath = &quot;src\/stream_client.rs&quot;\n\n\n[dependencies]\ntokio.workspace = true\ntonic = &quot;0.9&quot;\ntonic-reflection = &quot;0.9.2&quot;\nprost = &quot;0.11&quot;\ntokio-stream = &quot;0.1&quot;\nasync-stream = &quot;0.2&quot;\nserde = { version = &quot;1.0&quot;, features = [&quot;derive&quot;] }\nserde_json = &quot;1.0&quot;\nrand = &quot;0.7&quot;\nh2 = { version = &quot;0.3&quot; }\nanyhow = &quot;1.0.75&quot;\nfutures-util = &quot;0.3.28&quot;\n\n[build-dependencies]\ntonic-build = &quot;0.9&quot;\n\n\n\n<\/code><\/pre>\n<p>tonic \u7684\u793a\u4f8b\u4ee3\u7801\u8fd8\u662f\u6bd4\u8f83\u9f50\u5168\u7684\uff0c\u672c\u6b21\u6211\u4eec\u53c2\u8003 tonic \u7684 <a target=\"_blank\" href=\"https:\/\/link.segmentfault.com\/?enc=IlEkTD3Q9pK%2F1aUmsnHiTw%3D%3D.BAFIAhuzyvKS6C0OSCPzW7c2k3mmeWq%2B3vGlE4%2FCDRGtpGh%2FVYidWn%2FWRT1bgJPLzI%2FcarSqNljNAsx2pQuVQbzT1QvwPP6ImstgkqpvzNI%3D\" rel=\"noopener noreferrer\">streaming example<\/a>\u3002<\/p>\n<p>\u9996\u5148\u7f16\u5199 proto \u6587\u4ef6\uff0c\u7528\u6765\u63cf\u8ff0\u62a5\u6587\u3002 proto\/echo.proto<\/p>\n<pre><code class=\"proto\">syntax = &quot;proto3&quot;;\n\npackage stream;\n\n\/\/ EchoRequest is the request for echo.\nmessage EchoRequest { string message = 1; }\n\n\/\/ EchoResponse is the response for echo.\nmessage EchoResponse { string message = 1; }\n\n\/\/ Echo is the echo service.\nservice Echo {\n  \/\/ UnaryEcho is unary echo.\n  rpc UnaryEcho(EchoRequest) returns (EchoResponse) {}\n  \/\/ ServerStreamingEcho is server side streaming.\n  rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {}\n  \/\/ ClientStreamingEcho is client side streaming.\n  rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {}\n  \/\/ BidirectionalStreamingEcho is bidi streaming.\n  rpc BidirectionalStreamingEcho(stream EchoRequest)\n      returns (stream EchoResponse) {}\n}\n\n\n\n<\/code><\/pre>\n<p>\u6587\u4ef6\u5e76\u4e0d\u590d\u6742\uff0c\u53ea\u6709\u4e24\u4e2a message \u4e00\u4e2a\u8bf7\u6c42\u4e00\u4e2a\u8fd4\u56de\uff0c\u4e4b\u6240\u4ee5\u9009\u62e9\u8fd9\u4e2a\u793a\u4f8b\u662f\u56e0\u4e3a\u8be5\u793a\u4f8b\u5305\u542b\u4e86rpc\u4e2d\u7684\u6d41\u5f0f\u5904\u7406\uff0c\u5305\u6269\u4e86server \u6d41\u3001client \u6d41\u4ee5\u53ca\u53cc\u5411\u6d41\u7684\u64cd\u4f5c\u3002 <a target=\"_blank\" href=\"https:\/\/link.segmentfault.com\/?enc=URMcqlHBSL%2F2JuL9pO7Skw%3D%3D.y3rxBLfDcUcpD9c3RPjIbKvNad3ICs97hrbTABxuMA0%3D\" rel=\"noopener noreferrer\">\u7f16\u8f91build.rs<\/a> \u6587\u4ef6<\/p>\n<pre><code class=\"rust\">use std::{env, path::PathBuf};\n\nfn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {\n    tonic_build::compile_protos(&quot;proto\/echo.proto&quot;)?;\n    Ok(())\n}\n\n\n\n<\/code><\/pre>\n<p>\u8be5\u6587\u4ef6\u7528\u6765\u901a\u8fc7 tonic-build \u751f\u6210 grpc \u7684 rust \u57fa\u7840\u4ee3\u7801<\/p>\n<p>\u5b8c\u6210\u4e0a\u8ff0\u5de5\u4f5c\u540e\u5c31\u53ef\u4ee5\u6784\u5efa server \u548c client \u4ee3\u7801\u4e86<\/p>\n<p>stream_server.rs<\/p>\n<pre><code class=\"rust\">pub mod pb {\n    tonic::include_proto!(&quot;stream&quot;);\n}\n\nuse anyhow::Result;\nuse futures_util::FutureExt;\nuse pb::{EchoRequest, EchoResponse};\nuse std::{\n    error::Error,\n    io::ErrorKind,\n    net::{SocketAddr, ToSocketAddrs},\n    pin::Pin,\n    thread,\n    time::Duration,\n};\nuse tokio::{\n    net::TcpListener,\n    sync::{\n        mpsc,\n        oneshot::{self, Receiver, Sender},\n        Mutex,\n    },\n    task::{self, JoinHandle},\n};\nuse tokio_stream::{\n    wrappers::{ReceiverStream, TcpListenerStream},\n    Stream, StreamExt,\n};\nuse tonic::{transport::Server, Request, Response, Status, Streaming};\ntype EchoResult&lt;T&gt; = Result&lt;Response&lt;T&gt;, Status&gt;;\ntype ResponseStream = Pin&lt;Box&lt;dyn Stream&lt;Item = Result&lt;EchoResponse, Status&gt;&gt; + Send&gt;&gt;;\n\nfn match_for_io_error(err_status: &amp;Status) -&gt; Option&lt;&amp;std::io::Error&gt; {\n    let mut err: &amp;(dyn Error + 'static) = err_status;\n\n    loop {\n        if let Some(io_err) = err.downcast_ref::&lt;std::io::Error&gt;() {\n            return Some(io_err);\n        }\n\n        \/\/ h2::Error do not expose std::io::Error with `source()`\n        \/\/ https:\/\/github.com\/hyperium\/h2\/pull\/462\n        if let Some(h2_err) = err.downcast_ref::&lt;h2::Error&gt;() {\n            if let Some(io_err) = h2_err.get_io() {\n                return Some(io_err);\n            }\n        }\n\n        err = match err.source() {\n            Some(err) =&gt; err,\n            None =&gt; return None,\n        };\n    }\n}\n\n#[derive(Debug)]\npub struct EchoServer {}\n\n#[tonic::async_trait]\nimpl pb::echo_server::Echo for EchoServer {\n    async fn unary_echo(&amp;self, req: Request&lt;EchoRequest&gt;) -&gt; EchoResult&lt;EchoResponse&gt; {\n        let req_str = req.into_inner().message;\n\n        let response = EchoResponse { message: req_str };\n        Ok(Response::new(response))\n    }\n\n    type ServerStreamingEchoStream = ResponseStream;\n\n    async fn server_streaming_echo(\n        &amp;self,\n        req: Request&lt;EchoRequest&gt;,\n    ) -&gt; EchoResult&lt;Self::ServerStreamingEchoStream&gt; {\n        println!(&quot;EchoServer::server_streaming_echo&quot;);\n        println!(&quot;\/tclient connected from: {:?}&quot;, req.remote_addr());\n\n        \/\/ creating infinite stream with requested message\n        let repeat = std::iter::repeat(EchoResponse {\n            message: req.into_inner().message,\n        });\n        let mut stream = Box::pin(tokio_stream::iter(repeat).throttle(Duration::from_millis(200)));\n\n        let (tx, rx) = mpsc::channel(128);\n        tokio::spawn(async move {\n            while let Some(item) = stream.next().await {\n                match tx.send(Result::&lt;_, Status&gt;::Ok(item)).await {\n                    Ok(_) =&gt; {\n                        \/\/ item (server response) was queued to be send to client\n                    }\n                    Err(_item) =&gt; {\n                        \/\/ output_stream was build from rx and both are dropped\n                        break;\n                    }\n                }\n            }\n            println!(&quot;\/tclient disconnected&quot;);\n        });\n\n        let output_stream = ReceiverStream::new(rx);\n        Ok(Response::new(\n            Box::pin(output_stream) as Self::ServerStreamingEchoStream\n        ))\n    }\n\n    async fn client_streaming_echo(\n        &amp;self,\n        _: Request&lt;Streaming&lt;EchoRequest&gt;&gt;,\n    ) -&gt; EchoResult&lt;EchoResponse&gt; {\n        Err(Status::unimplemented(&quot;not implemented&quot;))\n    }\n\n    type BidirectionalStreamingEchoStream = ResponseStream;\n\n    async fn bidirectional_streaming_echo(\n        &amp;self,\n        req: Request&lt;Streaming&lt;EchoRequest&gt;&gt;,\n    ) -&gt; EchoResult&lt;Self::BidirectionalStreamingEchoStream&gt; {\n        println!(&quot;EchoServer::bidirectional_streaming_echo&quot;);\n\n        let mut in_stream = req.into_inner();\n        let (tx, rx) = mpsc::channel(128);\n\n        tokio::spawn(async move {\n            while let Some(result) = in_stream.next().await {\n                match result {\n                    Ok(v) =&gt; tx\n                        .send(Ok(EchoResponse { message: v.message }))\n                        .await\n                        .expect(&quot;working rx&quot;),\n                    Err(err) =&gt; {\n                        if let Some(io_err) = match_for_io_error(&amp;err) {\n                            if io_err.kind() == ErrorKind::BrokenPipe {\n                                eprintln!(&quot;\/tclient disconnected: broken pipe&quot;);\n                                break;\n                            }\n                        }\n\n                        match tx.send(Err(err)).await {\n                            Ok(_) =&gt; (),\n                            Err(_err) =&gt; break, \/\/ response was droped\n                        }\n                    }\n                }\n            }\n            println!(&quot;\/tstream ended&quot;);\n        });\n\n        \/\/ echo just write the same data that was received\n        let out_stream = ReceiverStream::new(rx);\n\n        Ok(Response::new(\n            Box::pin(out_stream) as Self::BidirectionalStreamingEchoStream\n        ))\n    }\n}\n\n#[tokio::main]\nasync fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {\n    \/\/ \u57fa\u7840server\n    let server = EchoServer {};\n    Server::builder()\n        .add_service(pb::echo_server::EchoServer::new(server))\n        .serve(&quot;0.0.0.0:50051&quot;.to_socket_addrs().unwrap().next().unwrap())\n        .await\n        .unwrap();\n    Ok(())\n}\n\n\n\n\n<\/code><\/pre>\n<p>server \u7aef\u7684\u4ee3\u7801\u8fd8\u662f\u6bd4\u8f83\u6e05\u6670\u7684\uff0c\u9996\u5148\u901a\u8fc7 tonic::include\/_proto! \u5b8f\u5f15\u5165grpc\u5b9a\u4e49\uff0c\u53c2\u6570\u662f proto \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684 package \u3002\u6211\u4eec\u91cd\u70b9\u8bf4\u8bf4 server\/_streaming\/_echo function \u3002\u8fd9\u4e2afunction \u7684\u5904\u7406\u6d41\u7a0b\u660e\u767d\u4e86\uff0c\u5176\u4ed6\u7684\u6d41\u5f0f\u5904\u7406\u5927\u540c\u5c0f\u5f02\u3002\u9996\u5148 \u901a\u8fc7std::iter::repeat function \u5b9a\u4e49\u4e00\u4e2a\u8fed\u4ee3\u5668\uff1b\u7136\u540e\u6784\u5efa tokio\/_stream \u5728\u672c\u793a\u4f8b\u4e2d \u6bcf 200\u6beb\u79d2\u4ea7\u751f\u4e00\u4e2a repeat\uff1b\u6700\u540e\u6784\u5efa\u4e00\u4e2a channel \uff0ctx \u7528\u6765\u53d1\u9001\u4ecestream\u4e2d\u83b7\u53d6\u7684\u5185\u5bb9\u592a\uff0crx \u5c01\u88c5\u5230response \u4e2d\u8fd4\u56de\u3002 \u6700\u540e main \u51fd\u6570 \u62c9\u8d77\u670d\u52a1\u3002<\/p>\n<p>client \u4ee3\u7801\u5982\u4e0b<\/p>\n<pre><code class=\"rust\">pub mod pb {\n    tonic::include_proto!(&quot;stream&quot;);\n}\n\nuse std::time::Duration;\nuse tokio_stream::{Stream, StreamExt};\nuse tonic::transport::Channel;\n\nuse pb::{echo_client::EchoClient, EchoRequest};\n\nfn echo_requests_iter() -&gt; impl Stream&lt;Item = EchoRequest&gt; {\n    tokio_stream::iter(1..usize::MAX).map(|i| EchoRequest {\n        message: format!(&quot;msg {:02}&quot;, i),\n    })\n}\n\nasync fn unary_echo(client: &amp;mut EchoClient&lt;Channel&gt;, num: usize) {\n    for i in 0..num {\n        let req = tonic::Request::new(EchoRequest {\n            message: &quot;msg&quot;.to_string() + &amp;i.to_string(),\n        });\n        let resp = client.unary_echo(req).await.unwrap();\n        println!(&quot;resp:{}&quot;, resp.into_inner().message);\n    }\n}\n\nasync fn streaming_echo(client: &amp;mut EchoClient&lt;Channel&gt;, num: usize) {\n    let stream = client\n        .server_streaming_echo(EchoRequest {\n            message: &quot;foo&quot;.into(),\n        })\n        .await\n        .unwrap()\n        .into_inner();\n\n    \/\/ stream is infinite - take just 5 elements and then disconnect\n    let mut stream = stream.take(num);\n    while let Some(item) = stream.next().await {\n        println!(&quot;\/treceived: {}&quot;, item.unwrap().message);\n    }\n    \/\/ stream is droped here and the disconnect info is send to server\n}\n\nasync fn bidirectional_streaming_echo(client: &amp;mut EchoClient&lt;Channel&gt;, num: usize) {\n    let in_stream = echo_requests_iter().take(num);\n\n    let response = client\n        .bidirectional_streaming_echo(in_stream)\n        .await\n        .unwrap();\n\n    let mut resp_stream = response.into_inner();\n\n    while let Some(received) = resp_stream.next().await {\n        let received = received.unwrap();\n        println!(&quot;\/treceived message: `{}`&quot;, received.message);\n    }\n}\n\nasync fn bidirectional_streaming_echo_throttle(client: &amp;mut EchoClient&lt;Channel&gt;, dur: Duration) {\n    let in_stream = echo_requests_iter().throttle(dur);\n\n    let response = client\n        .bidirectional_streaming_echo(in_stream)\n        .await\n        .unwrap();\n\n    let mut resp_stream = response.into_inner();\n\n    while let Some(received) = resp_stream.next().await {\n        let received = received.unwrap();\n        println!(&quot;\/treceived message: `{}`&quot;, received.message);\n    }\n}\n\n#[tokio::main]\nasync fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {\n    let mut client = EchoClient::connect(&quot;http:\/\/127.0.0.1:50051&quot;).await.unwrap();\n    println!(&quot;Unary echo:&quot;);\n    unary_echo(&amp;mut client, 10).await;\n    tokio::time::sleep(Duration::from_secs(1)).await;\n\n    println!(&quot;Streaming echo:&quot;);\n    streaming_echo(&amp;mut client, 5).await;\n    tokio::time::sleep(Duration::from_secs(1)).await; \/\/do not mess server println functions\n\n    \/\/ Echo stream that sends 17 requests then graceful end that connection\n    println!(&quot;\/r\/nBidirectional stream echo:&quot;);\n    bidirectional_streaming_echo(&amp;mut client, 17).await;\n\n    \/\/ Echo stream that sends up to `usize::MAX` requests. One request each 2s.\n    \/\/ Exiting client with CTRL+C demonstrate how to distinguish broken pipe from\n    \/\/ graceful client disconnection (above example) on the server side.\n    println!(&quot;\/r\/nBidirectional stream echo (kill client with CTLR+C):&quot;);\n    bidirectional_streaming_echo_throttle(&amp;mut client, Duration::from_secs(2)).await;\n\n    Ok(())\n}\n\n\n\n\n<\/code><\/pre>\n<p>\u6d4b\u8bd5\u4e00\u4e0b,\u5206\u522b\u8fd0\u884c server \u548c client<\/p>\n<pre><code class=\"shell\">cargo run --bin stream-server\ncargo run --bin stream-client\n\n\n\n<\/code><\/pre>\n<p>\u5728\u5f00\u53d1\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u4e0d\u4f1a\u518d client \u548c server\u90fd\u5f00\u53d1\u597d\u7684\u60c5\u51b5\u4e0b\u624d\u5f00\u59cb\u6d4b\u8bd5\u3002\u901a\u5e38\u5728\u5f00\u53d1server \u7aef\u7684\u65f6\u5019\u91c7\u7528 grpcurl \u5de5\u5177\u8fdb\u884c\u6d4b\u8bd5\u5de5\u4f5c<\/p>\n<pre><code class=\"shell\">grpcurl -import-path .\/proto -proto echo.proto list\ngrpcurl -import-path .\/proto -proto  echo.proto describe stream.Echo\ngrpcurl -plaintext -import-path .\/proto -proto  echo.proto -d '{&quot;message&quot;:&quot;1234&quot;}' 127.0.0.1:50051 stream.Echo\/UnaryEcho\n\n\n\n<\/code><\/pre>\n<p>\u6b64\u65f6\uff0c\u5982\u679c\u6211\u4eec\u4e0d\u6307\u5b9a -import-path \u53c2\u6570\uff0c\u6267\u884c\u5982\u4e0b\u547d\u4ee4<\/p>\n<pre><code class=\"shell\">grpcurl -plaintext 127.0.0.1:50051 list\n\n\n\n<\/code><\/pre>\n<p>\u4f1a\u51fa\u73b0\u5982\u4e0b\u62a5\u9519\u4fe1\u606f<\/p>\n<pre><code class=\"shell\">Failed to list services: server does not support the reflection API\n\n\n\n<\/code><\/pre>\n<h2>\u8ba9\u670d\u52a1\u7aef\u7a0b\u5e8f\u652f\u6301 reflection API<\/h2>\n<p><a target=\"_blank\" href=\"https:\/\/link.segmentfault.com\/?enc=rbVSMUVLrI5zpXJb%2B6pYIg%3D%3D.eWaphqsTKai21VBHR4XLhHtL9T5Sb%2FNlkMkn1PG%2BeIDCSAVnwZm2yX23AYdoKZCi\" rel=\"noopener noreferrer\">\u9996\u5148\u6539\u9020build.rs<\/a><\/p>\n<pre><code class=\"rust\">use std::{env, path::PathBuf};\n\nfn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {\n    let out_dir = PathBuf::from(env::var(&quot;OUT_DIR&quot;).unwrap());\n    tonic_build::configure()\n        .file_descriptor_set_path(out_dir.join(&quot;stream_descriptor.bin&quot;))\n        .compile(&amp;[&quot;proto\/echo.proto&quot;], &amp;[&quot;proto&quot;])\n        .unwrap();\n    Ok(())\n}\n\n\n\n<\/code><\/pre>\n<p>file\/_descriptor\/_set_path \u751f\u6210\u4e00\u4e2a\u6587\u4ef6\uff0c\u5176\u4e2d\u5305\u542b\u4e3a\u534f\u8bae\u7f13\u51b2\u6a21\u5757\u7f16\u7801\u7684 <code>prost_types::FileDescriptorSet<\/code> \u6587\u4ef6\u3002\u8fd9\u662f\u5b9e\u73b0 gRPC \u670d\u52a1\u5668\u53cd\u5c04\u6240\u5fc5\u9700\u7684\u3002<\/p>\n<p>\u63a5\u4e0b\u6765\u6539\u9020\u4e00\u4e0b <a target=\"_blank\" href=\"https:\/\/link.segmentfault.com\/?enc=SgCGe77F5MufVpw9meDrww%3D%3D.fYBEgL4AakVtG%2BxI23GN%2BJBQXYt6JM1dPmo9gxbGSDU%3D\" rel=\"noopener noreferrer\">stream-server.rs<\/a>\uff0c\u6d89\u53ca\u4e24\u5904\u66f4\u6539\u3002<\/p>\n<p>\u65b0\u589e STREAM\/_DESCRIPTOR\/_SET \u5e38\u91cf<\/p>\n<pre><code class=\"rust\">pub mod pb {\n    tonic::include_proto!(&quot;stream&quot;);\n    pub const STREAM_DESCRIPTOR_SET: &amp;[u8] =\n        tonic::include_file_descriptor_set!(&quot;stream_descriptor&quot;);\n}\n\n\n\n<\/code><\/pre>\n<p>\u4fee\u6539main\u51fd\u6570<\/p>\n<pre><code class=\"rust\">#[tokio::main]\nasync fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {\n    \/\/ \u57fa\u7840server\n    \/\/ let server = EchoServer {};\n    \/\/ Server::builder()\n    \/\/     .add_service(pb::echo_server::EchoServer::new(server))\n    \/\/     .serve(&quot;0.0.0.0:50051&quot;.to_socket_addrs().unwrap().next().unwrap())\n    \/\/     .await\n    \/\/     .unwrap();\n\n    \/\/ tonic_reflection \n    let service = tonic_reflection::server::Builder::configure()\n        .register_encoded_file_descriptor_set(pb::STREAM_DESCRIPTOR_SET)\n        .with_service_name(&quot;stream.Echo&quot;)\n        .build()\n        .unwrap();\n\n    let addr = &quot;0.0.0.0:50051&quot;.parse().unwrap();\n\n    let server = EchoServer {};\n\n    Server::builder()\n        .add_service(service)\n        .add_service(pb::echo_server::EchoServer::new(server))\n        .serve(addr)\n        .await?;\n    Ok(())\n}\n\n\n\n<\/code><\/pre>\n<p>register\/_encoded\/_file\/_descriptor\/_set \u5c06\u5305\u542b\u7f16\u7801\u7684 <code>prost_types::FileDescriptorSet<\/code> \u7684 byte slice \u6ce8\u518c\u5230 gRPC Reflection \u670d\u52a1\u751f\u6210\u5668\u6ce8\u518c\u3002<\/p>\n<p>\u518d\u6b21\u6d4b\u8bd5<\/p>\n<pre><code class=\"shell\">grpcurl -plaintext 127.0.0.1:50051 list\ngrpcurl -plaintext 127.0.0.1:50051 describe stream.Echo\n\n\n\n<\/code><\/pre>\n<p>\u8fd4\u56de\u6b63\u786e\u7ed3\u679c\u3002<\/p>\n<p><a target=\"_blank\" href=\"https:\/\/link.segmentfault.com\/?enc=27v1D7hViriEe9MB5TJrcQ%3D%3D.%2BiogiwYNXjAXcv4V1R2BILCjbPAW7CwcU5Zy3tGzVojrEtMN0NiLtcMuNh6haPb8VZSjmrY4%2BODC7NHWRr%2Bahw%3D%3D\" rel=\"noopener noreferrer\">\u4ee5\u4e0a\u5b8c\u6574\u4ee3\u7801\u5730\u5740<\/a><\/p>\n<blockquote>\n<p>\u4f5c\u8005\uff1a\u4eac\u4e1c\u79d1\u6280 \u8d3e\u4e16\u95fb<\/p>\n<p>\u6765\u6e90\uff1a\u4eac\u4e1c\u4e91\u5f00\u53d1\u8005\u793e\u533a \u8f6c\u8f7d\u8bf7\u6ce8\u660e\u6765\u6e90<\/p>\n<\/blockquote>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000044227389\">https:\/\/segmentfault.com\/a\/1190000044227389<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>gRPC \u662f\u5f00\u53d1\u4e2d\u5e38\u7528\u7684\u5f00\u6e90\u9ad8\u6027\u80fd\u8fdc\u7a0b\u8fc7\u7a0b\u8c03\u7528\uff08RPC\uff09\u6846\u67b6\uff0ctonic \u662f\u57fa\u4e8e HTTP\/2 \u7684 gRPC  [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10008279","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10008279","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10008279"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10008279\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10008279"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10008279"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10008279"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}