編寫一個go gRPC的服務


前置條件:

獲取 gRPC-go 源碼

$ go get google.golang.org/grpc

簡單例子的源碼位置:

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

復雜些例子的源碼位置:

$ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide

寫一個gRPC的服務,一般分下面幾步:

  • 在一個 .proto 文件內定義服務。
  • 用 protocol buffer 編譯器生成服務器和客戶端代碼。
  • 使用 gRPC 的 Go API 為你的服務實現一個簡單的客戶端和服務器。

定義服務

在一個 .proto 文件中定義服務

簡單的例子服務定義在: examples/helloworld/helloworld/helloworld.proto

route_guide 的定義在 : examples/route_guide/routeguide/route_guide.proto

要定義一個服務,你必須在你的 .proto 文件中指定 service:

image

然后在你的服務中定義 rpc 方法,指定請求的和響應類型。

gRPC 允許你定義4種類型的 service 方法,這些都在 RouteGuide 服務中使用到了:

簡單RPC

一個 簡單 RPC , 客戶端發送帶參請求到服務器並等待響應返回,就像平常的函數調用一樣。

image

服務器端流式 RPC

一個 服務器端流式 RPC , 客戶端發送請求到服務器,拿到一個流去讀取返回的消息序列。 客戶端讀取返回的流,直到里面沒有任何消息。從例子中可以看出,通過在 響應 類型前插入 stream 關鍵字,可以指定一個服務器端的流方法。

image

客戶端流式 RPC

一個 客戶端流式 RPC , 客戶端寫入一個消息序列並將其發送到服務器,同樣也是使用流。一旦客戶端完成寫入消息,它等待服務器完成讀取返回它的響應。通過在 請求 類型前指定 stream 關鍵字來指定一個客戶端的流方法。

image

雙向流式 RPC

一個 雙向流式 RPC 是雙方使用讀寫流去發送一個消息序列。兩個流獨立操作,因此客戶端和服務器可以以任意喜歡的順序讀寫:比如, 服務器可以在寫入響應前等待接收所有的客戶端消息,或者可以交替的讀取和寫入消息,或者其他讀寫的組合。 每個流中的消息順序被預留。你可以通過在請求和響應前加 stream 關鍵字去制定方法的類型。

image

消息類型

上面看起來像函數參數、返回值得,由於要涉及到跨服務器調用,這些其實傳遞的是消息。我們的 .proto 文件也包含了所有請求的 protocol buffer 消息類型定義以及在服務方法中使用的響應類型——比如,下面的Point消息類型:

image

 

生成服務器和客戶端代碼

我們需要通過 protocol buffer 的編譯器 protoc 以及一個特殊的 gRPC Go 插件來完成用 protocol buffer 編譯器生成服務器和客戶端代碼。

 

簡單期間,有個 bash 腳本可以幫我們生成合適的代碼  codegen.sh  (https://github.com/grpc/grpc-go/blob/master/codegen.sh)

 

image

運行 codegen.sh route_guide.proto 就可以在當前目錄下產生 route_guide.pb.go 文件。

在這個產生的文件中, 既有 routeGuideClient 客戶端代碼部分, 也有 routeGuideRouteChatServer 服務器段代碼部分。

創建服務器

這部分的源碼在: grpc-go/examples/route_guide/server/server.go

讓 RouteGuide 服務工作有兩個部分:

  • 實現我們服務定義的生成的服務接口:做我們的服務的實際的“工作”。
  • 運行一個 gRPC 服務器,監聽來自客戶端的請求並返回服務的響應。

 

實現RouteGuide

在源碼中,我們可以看到實現了接口RouteGuideServer的routeGuideServer數據結構。  這個接口是在route_guide.pb.go中自動產生的。

簡單RPC

GetFeature,它從客戶端拿到一個 Point 對象,然后從返回包含從數據庫拿到的feature信息的 Feature.

image
該方法傳入了 RPC 的上下文對象,以及客戶端的 Point 參數。它返回了Feature 響應信息和error信息。

在方法中我們遍歷所有服務器端保存的信息,找到位置信息匹配的,然后將其和一個nil錯誤一起返回給客戶端。

 

服務器端流式 RPC

ListFeatures 是一個服務器端的流式 RPC,我們將多個 Feature 發回給客戶端。

image

這里的請求參數是一個 Rectangle,客戶端期望返回多個 Feature,這次我們使用了一個請求對象和一個特殊的RouteGuide_ListFeaturesServer來寫入我們的響應,而不是得到方法參數中的入參和返回值。
在這個方法中,我們填充了盡可能多的 Feature 對象去返回,用steam的 Send() 方法把它們寫入 RouteGuide_ListFeaturesServer。

最后,我們返回了一個 nil 錯誤告訴 gRPC 響應的寫入已經完成。

如果在調用過程中發生任何錯誤,我們會返回一個非 nil 的錯誤;

 

客戶端流式 RPC

客戶端流方法 RecordRoute,我們通過它可以從客戶端拿到一個 Point 的流,其中包括我們需要的Point信息。

這次這個方法用了一個 RouteGuide_RecordRouteServer 流,服務器可以用它來同時讀 和 寫消息——它可以用自己的 Recv() 方法接收客戶端消息並且用 SendAndClose() 方法返回它的單個響應。

image

在方法體中,我們使用 RouteGuide_RecordRouteServer 的 Recv() 方法去反復讀取客戶端的請求到一個請求對象(在這個場景下是 Point),直到沒有更多的消息。

服務器需要在每次調用后檢查 Read() 返回的錯誤。如果返回值為 nil,流依然完好,可以繼續讀取;

如果返回值為 io.EOF,消息流結束,服務器可以返回它的 RouteSummary。

如果它還有其它值,我們原樣返回錯誤,gRPC 層會把它轉換為 RPC 狀態。

 

雙向流式 RPC

雙向流式 RPC RouteChat()。

image

這里讀寫的語法和客戶端流方法相似,除了服務器會使用流的 Send() 方法而不是 SendAndClose(),因為它需要寫多個響應。雖然客戶端和服務器端總是會拿到對方寫入時順序的消息,它們可以以任意順序讀寫——流的操作是完全獨立的。

 

啟動服務器

一旦我們實現了所有的方法,我們還需要啟動一個gRPC服務器,這樣客戶端才可以使用服務。

image

為了構建和啟動服務器,我們需要:

  • 使用 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 指定我們期望客戶端請求的監聽端口。
  • 使用grpc.NewServer()創建 gRPC 服務器的一個實例。
  • 在 gRPC 服務器注冊我們的服務實現。
  • 用服務器 Serve() 方法以及我們的端口信息區實現阻塞等待,直到進程被殺死或者 Stop() 被調用。

 

創建客戶端

建立跟服務器的連接

為了調用服務方法,我們首先創建一個 gRPC conn。我們通過給 grpc.Dial() 傳入服務器地址和端口號做到這點,如下:

image

你可以使用 DialOptions 在 grpc.Dial 中設置授權認證(如, TLS,GCE認證,JWT認證),如果服務有這樣的要求的話 —— 但是對於 RouteGuide 服務,我們不用這么做。

一旦 gRPC conn 建立起來,我們需要一個client去執行 RPC。我們通過 .proto 生成的 pb 包提供的 NewRouteGuideClient 方法來完成。
image

調用服務器方法

簡單RPC

調用簡單 RPC GetFeature 幾乎是和調用一個本地方法一樣直觀。

image

服務器端流式 RPC

image

我們給方法傳入一個上下文和請求。然而,我們得到返回的是一個 RouteGuide_ListFeaturesClient 實例,而不是一個應答對象。客戶端可以使用 RouteGuide_ListFeaturesClient 流去讀取服務器的響應。

我們使用 RouteGuide_ListFeaturesClientRecv() 方法去反復讀取服務器的響應到一個響應 protocol buffer 對象(在這個場景下是Feature)直到消息讀取完畢:每次調用完成時,客戶端都要檢查從 Recv() 返回的錯誤 err。如果返回為 nil,流依然完好並且可以繼續讀取;如果返回為 io.EOF,則說明消息流已經結束;否則就一定是一個通過 err 傳過來的 RPC 錯誤。

客戶端流式 RPC

image

RouteGuide_RecordRouteClient 有一個 Send() 方法,我們可以用它來給服務器發送請求。一旦我們完成使用 Send() 方法將客戶端請求寫入流,就需要調用流的 CloseAndRecv()方法,讓 gRPC 知道我們已經完成了寫入同時期待返回應答。我們從 CloseAndRecv() 返回的 err 中獲得 RPC 的狀態。如果狀態為nil,那么CloseAndRecv()的第一個返回值將會是合法的服務器應答。


雙向流式 RPC

image

我們只給函數傳入一個上下文對象,拿到可以用來讀寫的流。

運行例子

服務器端:

$ go run server/server.go

客戶端:

$ go run client/client.go

 

參考資料:

中文 gRPC 基礎 Go   http://doc.oschina.net/grpc?t=60133 

英文 gRPC 基礎 Go  http://www.grpc.io/docs/tutorials/basic/go.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM