<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                > From: http://doc.oschina.net/grpc?t=60133 ## 為什么使用 gRPC? 我們的例子是一個簡單的路由映射的應用,它允許客戶端獲取路由特性的信息,生成路由的總結,以及交互路由信息,如服務器和其他客戶端的流量更新。 有了 gRPC, 我們可以一次性的在一個 .proto 文件中定義服務并使用任何支持它的語言去實現客戶端和服務器,反過來,它們可以在各種環境中,從Google的服務器到你自己的平板電腦—— gRPC 幫你解決了不同語言及環境間通信的復雜性.使用 protocol buffers 還能獲得其他好處,包括高效的序列號,簡單的 IDL 以及容易進行接口更新。 ## 例子的代碼和設置 教程的代碼在這里 [grpc/grpc-go/examples/cpp/route\_guide](https://github.com/grpc/grpc-go/tree/master/examples/route_guide)。 要下載例子,通過運行下面的命令去克隆`grpc-go`代碼庫: ~~~ $ go get google.golang.org/grpc ~~~ 然后改變當前的目錄到 `grpc-go/examples/route_guide`: ~~~ $ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide ~~~ 你還需要安裝生成服務器和客戶端的接口代碼相關工具-如果你還沒有安裝的話,請查看下面的設置指南 [Go快速開始指南](http://doc.oschina.net/docs/installation/go.html)。 ## 定義服務 我們的第一步(可以從[概覽](http://doc.oschina.net/docs/index.html)中得知)是使用 [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview)去定義 gRPC *service* 和方法 *request* 以及 *response* 的類型。你可以在[`examples/protos/route_guide.proto`](https://github.com/grpc/grpc/blob/{{ site.data.config.grpc_release_branch }}/examples/protos/route_guide.proto)看到完整的 .proto 文件。 要定義一個服務,你必須在你的 .proto 文件中指定 `service`: ~~~ service RouteGuide { ... } ~~~ 然后在你的服務中定義 `rpc` 方法,指定請求的和響應類型。gRPC 允許你定義4種類型的 service 方法,這些都在 `RouteGuide` 服務中使用: * 一個 *簡單 RPC* , 客戶端使用存根發送請求到服務器并等待響應返回,就像平常的函數調用一樣。 ~~~ // Obtains the feature at a given position. rpc GetFeature(Point) returns (Feature) {} ~~~ * 一個 *服務器端流式 RPC* , 客戶端發送請求到服務器,拿到一個流去讀取返回的消息序列。 客戶端讀取返回的流,直到里面沒有任何消息。從例子中可以看出,通過在 *響應* 類型前插入 `stream` 關鍵字,可以指定一個服務器端的流方法。 ~~~ // Obtains the Features available within the given Rectangle. Results are // streamed rather than returned at once (e.g. in a response message with a // repeated field), as the rectangle may cover a large area and contain a // huge number of features. rpc ListFeatures(Rectangle) returns (stream Feature) {} ~~~ * 一個 *客戶端流式 RPC* , 客戶端寫入一個消息序列并將其發送到服務器,同樣也是使用流。一旦客戶端完成寫入消息,它等待服務器完成讀取返回它的響應。通過在 *請求* 類型前指定 `stream` 關鍵字來指定一個客戶端的流方法。 ~~~ // Accepts a stream of Points on a route being traversed, returning a // RouteSummary when traversal is completed. rpc RecordRoute(stream Point) returns (RouteSummary) {} ~~~ * 一個 *雙向流式 RPC* 是雙方使用讀寫流去發送一個消息序列。兩個流獨立操作,因此客戶端和服務器可以以任意喜歡的順序讀寫:比如, 服務器可以在寫入響應前等待接收所有的客戶端消息,或者可以交替的讀取和寫入消息,或者其他讀寫的組合。 每個流中的消息順序被預留。你可以通過在請求和響應前加 `stream` 關鍵字去制定方法的類型。 ~~~ // Accepts a stream of RouteNotes sent while a route is being traversed, // while receiving other RouteNotes (e.g. from other users). rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} ~~~ 我們的 .proto 文件也包含了所有請求的 protocol buffer 消息類型定義以及在服務方法中使用的響 應類型——比如,下面的`Point`消息類型: ~~~ // Points are represented as latitude-longitude pairs in the E7 representation // (degrees multiplied by 10**7 and rounded to the nearest integer). // Latitudes should be in the range +/- 90 degrees and longitude should be in // the range +/- 180 degrees (inclusive). message Point { int32 latitude = 1; int32 longitude = 2; } ~~~ ## 生成客戶端和服務器端代碼 接下來我們需要從 .proto 的服務定義中生成 gRPC 客戶端和服務器端的接口。我們通過 protocol buffer 的編譯器 `protoc` 以及一個特殊的 gRPC Go 插件來完成。 簡單起見,我們提供一個 [bash 腳本](https://github.com/grpc/grpc-go/blob/master/codegen.sh) 幫你用合適的插件,輸入,輸出去運行 `protoc`(如果你想自己去運行,確保你已經安裝了 protoc,并且請遵循下面的 gRPC-Go [安裝指南](https://github.com/grpc/grpc-go/blob/master/README.md))來操作: ~~~ $ codegen.sh route_guide.proto ~~~ 實際上運行的是: ~~~ $ protoc --go_out=plugins=grpc:. route_guide.proto ~~~ 運行這個命令可以在當前目錄中生成下面的文件: * `route_guide.pb.go` 這些包括: * 所有用于填充,序列化和獲取我們請求和響應消息類型的 protocol buffer 代碼 * 一個為客戶端調用定義在`RouteGuide`服務的方法的接口類型(或者 *存根* ) * 一個為服務器使用定義在`RouteGuide`服務的方法去實現的接口類型(或者 *存根* ) ## 創建服務器 首先來看看我們如何創建一個 `RouteGuide` 服務器。如果你只對創建 gRPC 客戶端感興趣,你可以跳 過這個部分,直接到[創建客戶端](http://doc.oschina.net/grpc?t=60133#client) (當然你也可能發現它也很有意思)。 讓 `RouteGuide` 服務工作有兩個部分: * 實現我們服務定義的生成的服務接口:做我們的服務的實際的“工作”。 * 運行一個 gRPC 服務器,監聽來自客戶端的請求并返回服務的響應。 你可以從[grpc-go/examples/route\_guide/server/server.go](https://github.com/grpc/grpc-go/tree/master/examples/route_guide/server/server.go)看到我們的 `RouteGuide` 服務器的實現代碼。現在讓我們近距離研究它是如何工作的。 ### 實現RouteGuide 我們可以看出,服務器有一個實現了生成的 `RouteGuideServer` 接口的 `routeGuideServer` 結構類型: ~~~ type routeGuideServer struct { ... } ... func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { ... } ... func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { ... } ... func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { ... } ... func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { ... } ... ~~~ #### 簡單 RPC `routeGuideServer` 實現了我們所有的服務方法。首先讓我們看看最簡單的類型 `GetFeature`,它從客戶端拿到一個 `Point` 對象,然后從返回包含從數據庫拿到的feature信息的 `Feature`. ~~~ func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { for _, feature := range s.savedFeatures { if proto.Equal(feature.Location, point) { return feature, nil } } // No feature was found, return an unnamed feature return &pb.Feature{"", point}, nil } ~~~ 該方法傳入了 RPC 的上下文對象,以及客戶端的 `Point` protocol buffer請求。它返回了一個包含響應信息和`error` 的 `Feature` protocol buffer對象。在方法中我們用適當的信息填充 `Feature`,然后將其和一個`nil`錯誤一起返回,告訴 gRPC 我們完成了對 RPC 的處理,并且 `Feature` 可以返回給客戶端。 #### 服務器端流式 RPC 現在讓我們來看看我們的一種流式 RPC。 `ListFeatures` 是一個服務器端的流式 RPC,所以我們需要將多個 `Feature` 發回給客戶端。 ~~~ func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { for _, feature := range s.savedFeatures { if inRange(feature.Location, rect) { if err := stream.Send(feature); err != nil { return err } } } return nil } ~~~ 如你所見,這里的請求對象是一個 `Rectangle`,客戶端期望從中找到 `Feature`,這次我們得到了一個請求對象和一個特殊的`RouteGuide_ListFeaturesServer`來寫入我們的響應,而不是得到方法參數中的簡單請求和響應對象。 在這個方法中,我們填充了盡可能多的 `Feature` 對象去返回,用它們的 `Send()` 方法把它們寫入 `RouteGuide_ListFeaturesServer`。最后,在我們的簡單 RPC中,我們返回了一個 `nil` 錯誤告訴 gRPC 響應的寫入已經完成。如果在調用過程中發生任何錯誤,我們會返回一個非 `nil` 的錯誤;gRPC 層會將其轉化為合適的 RPC 狀態通過線路發送。 #### 客戶端流式 RPC 現在讓我們看看稍微復雜點的東西:客戶端流方法 `RecordRoute`,我們通過它可以從客戶端拿到一個 `Point` 的流,其中包括它們路徑的信息。如你所見,這次這個方法沒有請求參數。相反的,它拿到了一個 `RouteGuide_RecordRouteServer` 流,服務器可以用它來同時讀 *和* 寫消息——它可以用自己的 `Recv()` 方法接收客戶端消息并且用 `SendAndClose()` 方法返回它的單個響應。 ~~~ func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { var pointCount, featureCount, distance int32 var lastPoint *pb.Point startTime := time.Now() for { point, err := stream.Recv() if err == io.EOF { endTime := time.Now() return stream.SendAndClose(&pb.RouteSummary{ PointCount: pointCount, FeatureCount: featureCount, Distance: distance, ElapsedTime: int32(endTime.Sub(startTime).Seconds()), }) } if err != nil { return err } pointCount++ for _, feature := range s.savedFeatures { if proto.Equal(feature.Location, point) { featureCount++ } } if lastPoint != nil { distance += calcDistance(lastPoint, point) } lastPoint = point } } ~~~ 在方法體中,我們使用 `RouteGuide_RecordRouteServer` 的 `Recv()` 方法去反復讀取客戶端的請求到一個請求對象(在這個場景下是 `Point`),直到沒有更多的消息:服務器需要在每次調用后檢查 `Read()` 返回的錯誤。如果返回值為 `nil`,流依然完好,可以繼續讀取;如果返回值為 `io.EOF`,消息流結束,服務器可以返回它的 `RouteSummary`。如果它還有其它值,我們原樣返回錯誤,gRPC 層會把它轉換為 RPC 狀態。 #### 雙向流式 RPC 最后,讓我們看看雙向流式 RPC `RouteChat()`。 ~~~ func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { for { in, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err } key := serialize(in.Location) ... // look for notes to be sent to client for _, note := range s.routeNotes[key] { if err := stream.Send(note); err != nil { return err } } } } ~~~ 這次我們得到了一個 `RouteGuide_RouteChatServer` 流,和我們的客戶端流的例子一樣,它可以用來讀寫消息。但是,這次當客戶端還在往 *它們* 的消息流中寫入消息時,我們通過方法的流返回值。 這里讀寫的語法和客戶端流方法相似,除了服務器會使用流的 `Send()` 方法而不是 `SendAndClose()`,因為它需要寫多個響應。雖然客戶端和服務器端總是會拿到對方寫入時順序的消息,它們可以以任意順序讀寫——流的操作是完全獨立的。 ### 啟動服務器 一旦我們實現了所有的方法,我們還需要啟動一個gRPC服務器,這樣客戶端才可以使用服務。下面這段代碼展示了在我們`RouteGuide`服務中實現的過程: ~~~ flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{}) ... // determine whether to use TLS grpcServer.Serve(lis) ~~~ 為了構建和啟動服務器,我們需要: 1. 使用 `lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))` 指定我們期望客戶端請求的監聽端口。 2. 使用`grpc.NewServer()`創建 gRPC 服務器的一個實例。 3. 在 gRPC 服務器注冊我們的服務實現。 4. 用服務器 `Serve()` 方法以及我們的端口信息區實現阻塞等待,直到進程被殺死或者 `Stop()` 被調用。 ## 創建客戶端 在這部分,我們將嘗試為 `RouteGuide` 服務創建一個 Go 的客戶端。你可以從[grpc-go/examples/route\_guide/client/client.go](https://github.com/grpc/grpc-go/tree/master/examples/route_guide/client/client.go)看到我們完整的客戶端例子代碼. ### 創建存根 為了調用服務方法,我們首先創建一個 gRPC *channel* 和服務器交互。我們通過給 `grpc.Dial()` 傳入服務器地址和端口號做到這點,如下: ~~~ conn, err := grpc.Dial(*serverAddr) if err != nil { ... } defer conn.Close() ~~~ 你可以使用 `DialOptions` 在 `grpc.Dial` 中設置授權認證(如, TLS,GCE認證,JWT認證),如果服務有這樣的要求的話 —— 但是對于 `RouteGuide` 服務,我們不用這么做。 一旦 gRPC *channel* 建立起來,我們需要一個客戶端 *存根* 去執行 RPC。我們通過 .proto 生成的 `pb` 包提供的 `NewRouteGuideClient` 方法來完成。 ~~~ client := pb.NewRouteGuideClient(conn) ~~~ ### 調用服務方法 現在讓我們看看如何調用服務方法。注意,在 gRPC-Go 中,RPC以阻塞/同步模式操作,這意味著 RPC 調用等待服務器響應,同時要么返回響應,要么返回錯誤。 #### 簡單 RPC 調用簡單 RPC `GetFeature` 幾乎是和調用一個本地方法一樣直觀。 ~~~ feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906}) if err != nil { ... } ~~~ 如你所見,我們調用了前面創建的存根上的方法。在我們的方法參數中,我們創建并且填充了一個請求的 protocol buffer 對象(例子中為 `Point`)。我們同時傳入了一個 `context.Context` ,在有需要時可以讓我們改變 RPC 的行為,比如超時/取消一個正在運行的 RPC。 如果調用沒有返回錯誤,那么我們就可以從服務器返回的第一個返回值中讀到響應信息。 ~~~ log.Println(feature) ~~~ #### 服務器端流式 RPC `ListFeatures` 就是我們說的服務器端流方法,它會返回地理的`Feature` 流。 如果你已經讀過[創建服務器](http://doc.oschina.net/grpc?t=60133#server),本節的一些內容也許看上去會很熟悉——流式 RPC 是在客戶端和服務器兩端以一種類似的方式實現的。 ~~~ rect := &pb.Rectangle{ ... } // initialize a pb.Rectangle stream, err := client.ListFeatures(context.Background(), rect) if err != nil { ... } for { feature, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalf("%v.ListFeatures(_) = _, %v", client, err) } log.Println(feature) } ~~~ 在簡單 RPC 的例子中,我們給方法傳入一個上下文和請求。然而,我們得到返回的是一個 `RouteGuide_ListFeaturesClient` 實例,而不是一個應答對象。客戶端可以使用 `RouteGuide_ListFeaturesClient` 流去讀取服務器的響應。 我們使用 `RouteGuide_ListFeaturesClient` 的 `Recv()` 方法去反復讀取服務器的響應到一個響應 protocol buffer 對象(在這個場景下是`Feature`)直到消息讀取完畢:每次調用完成時,客戶端都要檢查從 `Recv()` 返回的錯誤 `err`。如果返回為 `nil`,流依然完好并且可以繼續讀取;如果返回為 `io.EOF`,則說明消息流已經結束;否則就一定是一個通過 `err` 傳過來的 RPC 錯誤。 #### 客戶端流式 RPC 除了我們需要給方法傳入一個上下文而后返回 `RouteGuide_RecordRouteClient` 流以外,客戶端流方法 `RecordRoute` 和服務器端方法類似,它可以用來讀 *和* 寫消息。 ~~~ // Create a random number of random points r := rand.New(rand.NewSource(time.Now().UnixNano())) pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points var points []*pb.Point for i := 0; i < pointCount; i++ { points = append(points, randomPoint(r)) } log.Printf("Traversing %d points.", len(points)) stream, err := client.RecordRoute(context.Background()) if err != nil { log.Fatalf("%v.RecordRoute(_) = _, %v", client, err) } for _, point := range points { if err := stream.Send(point); err != nil { log.Fatalf("%v.Send(%v) = %v", stream, point, err) } } reply, err := stream.CloseAndRecv() if err != nil { log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } log.Printf("Route summary: %v", reply) ~~~ `RouteGuide_RecordRouteClient` 有一個 `Send()` 方法,我們可以用它來給服務器發送請求。一旦我們完成使用 `Send()` 方法將客戶端請求寫入流,就需要調用流的 `CloseAndRecv()`方法,讓 gRPC 知道我們已經完成了寫入同時期待返回應答。我們從 `CloseAndRecv()` 返回的 `err` 中獲得 RPC 的狀態。如果狀態為`nil`,那么`CloseAndRecv()`的第一個返回值將會是合法的服務器應答。 #### 雙向流式 RPC 最后,讓我們看看雙向流式 RPC `RouteChat()`。 和 `RecordRoute` 的場景類似,我們只給函數傳 入一個上下文對象,拿到可以用來讀寫的流。但是,當服務器依然在往 *他們* 的消息流寫入消息時,我們 通過方法流返回值。 ~~~ stream, err := client.RouteChat(context.Background()) waitc := make(chan struct{}) go func() { for { in, err := stream.Recv() if err == io.EOF { // read done. close(waitc) return } if err != nil { log.Fatalf("Failed to receive a note : %v", err) } log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude) } }() for _, note := range notes { if err := stream.Send(note); err != nil { log.Fatalf("Failed to send a note: %v", err) } } stream.CloseSend() <-waitc ~~~ 這里讀寫的語法和我們的客戶端流方法很像,除了在完成調用時,我們會使用流的 `CloseSend()` 方法。 雖然每一端獲取對方信息的順序和信息被寫入的順序一致,客戶端和服務器都可以以任意順序讀寫——流的操作是完全獨立的。 ## 來試試吧! 假設你在 `$GOPATH/src/google.golang.org/grpc/examples/route_guide` 目錄,要編譯和運行服務器,只需要運行: ~~~ $ go run server/server.go ~~~ 同樣的,運行客戶端: ~~~ $ go run client/client.go ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看