1. 簡介
在本程式碼研究室中,您將使用 gRPC-Go 建立用戶端和伺服器,做為以 Go 撰寫的路線對應應用程式基礎。
完成本教學課程後,您將擁有一個用戶端,可使用 gRPC 連線至遠端伺服器,取得地圖上特定座標位置的名稱或郵寄地址。完整的應用程式可能會使用這種用戶端/伺服器設計,列舉或摘要說明路線上的興趣點。
服務定義於 Protocol Buffers 檔案中,用於產生用戶端和伺服器的樣板程式碼,以便彼此通訊,節省您實作該功能的時間和精力。
這段產生的程式碼不僅會處理伺服器與用戶端之間複雜的通訊,也會處理資料序列化和還原序列化。
課程內容
- 如何使用通訊協定緩衝區定義服務 API。
- 如何使用自動程式碼生成功能,從通訊協定緩衝區定義建構以 gRPC 為基礎的用戶端和伺服器。
- 瞭解如何透過 gRPC 進行用戶端與伺服器之間的通訊。
這個程式碼研究室適合剛接觸 gRPC 的 Go 開發人員、想複習 gRPC 的使用者,以及有興趣建構分散式系統的任何人。不需要有 gRPC 相關經驗。
2. 事前準備
必要條件
請確認已安裝下列項目:
- Go 工具鍊 1.24.5 以上版本。如需安裝操作說明,請參閱 Go 的入門指南。
- 通訊協定緩衝區編譯器
protoc
3.27.1 以上版本。如需安裝操作說明,請參閱編譯器的安裝指南。 - Go 和 gRPC 適用的通訊協定緩衝區編譯器外掛程式。如要安裝這些外掛程式,請執行下列指令:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
更新 PATH
變數,讓通訊協定緩衝區編譯器可以找到外掛程式:
export PATH="$PATH:$(go env GOPATH)/bin"
取得程式碼
為避免您必須從頭開始,本程式碼研究室提供應用程式原始碼的架構,供您完成。下列步驟將說明如何完成應用程式,包括使用通訊協定緩衝區編譯器外掛程式產生樣板 gRPC 程式碼。
從 GitHub 將這個原始碼下載為 .ZIP 封存檔,然後解壓縮內容。
或者,如果您想略過輸入實作內容,也可以在 GitHub 上取得完整的原始碼。
3. 定義服務
首先,請使用通訊協定緩衝區定義應用程式的 gRPC 服務、RPC 方法,以及要求和回應訊息類型。你的服務將提供:
- 伺服器實作且用戶端呼叫的 RPC 方法,稱為
GetFeature
。 - 使用
GetFeature
方法時,用戶端和伺服器之間交換的資料結構為Point
和Feature
訊息類型。用戶端會在傳送至伺服器的GetFeature
要求中,以Point
形式提供地圖座標,而伺服器會回覆相應的Feature
,說明這些座標所指的位置。
這個 RPC 方法及其訊息型別都會在提供的原始碼 routeguide/route_guide.proto
檔案中定義。
通訊協定緩衝區通常稱為 protobuf。如要進一步瞭解 gRPC 術語,請參閱 gRPC 的「核心概念、架構和生命週期」。
訊息類型
在原始碼的 routeguide/route_guide.proto
檔案中,請先定義 Point
訊息型別。Point
代表地圖上的經緯度座標組合。在本程式碼研究室中,請使用整數做為座標:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
數字 1
和 2
是 message
結構中每個欄位的專屬 ID 編號。
接著,定義 Feature
訊息類型。Feature
會使用 string
欄位,指定 Point
所指定位置的名稱或郵寄地址:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
服務方法
route_guide.proto
檔案具有名為 RouteGuide
的 service
結構,可定義應用程式服務提供的一或多個方法。
在 RouteGuide
定義中新增 rpc
方法 GetFeature
。如先前所述,這個方法會根據一組指定座標查閱地點名稱或地址,因此請讓 GetFeature
針對指定 Point
傳回 Feature
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
這是 unary RPC 方法:簡單的 RPC,用戶端會將要求傳送至伺服器,並等待伺服器傳回回應,就像呼叫本機函式一樣。
4. 產生用戶端和伺服器程式碼
接著,使用通訊協定緩衝區編譯器,從 .proto
檔案產生用戶端和伺服器的樣板 gRPC 程式碼。在 routeguide
目錄中執行:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
這個指令會產生下列檔案:
route_guide.pb.go
,其中包含建立應用程式訊息類型及存取其資料的函式。route_guide_grpc.pb.go
,其中包含用戶端用來呼叫服務遠端 gRPC 方法的函式,以及伺服器用來提供該遠端服務的函式。
接著,我們會在伺服器端實作 GetFeature
方法,以便在用戶端傳送要求時,伺服器可以回覆答案。
5. 實作服務
伺服器端的 GetFeature
函式會完成主要工作:從用戶端接收 Point
訊息,並從已知地點清單中找出對應的位置資訊,然後以 Feature
訊息的形式傳回。以下是 server/server.go
中的函式實作方式:
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{Location: point}, nil
}
在遠端用戶端提出要求後叫用這個方法時,函式會收到描述 RPC 呼叫的 Context
物件,以及來自該用戶端要求的 Point
通訊協定緩衝區物件。函式會傳回所查閱位置的 Feature
通訊協定緩衝區物件,並視需要傳回 error
。
在這個方法中,請使用指定 Point
的適當資訊填入 Feature
物件,然後連同 nil
錯誤一併 return
,告知 gRPC 您已完成處理 RPC,且 Feature
物件可以傳回給用戶端。
GetFeature
方法需要建立及註冊 routeGuideServer
物件,這樣一來,用戶端的位置查詢要求就能傳送至該函式。請在 main()
中執行這項操作:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
以下是 main()
的運作方式:
- 使用
lis, err := net.Listen(...)
指定要用來監聽遠端用戶端要求的 TCP 通訊埠。根據預設,應用程式會使用變數port
指定的 TCP 通訊埠50051
,或在執行伺服器時,透過指令列傳遞--port
切換。如果無法開啟 TCP 連接埠,應用程式會以嚴重錯誤結束。 - 使用
grpc.NewServer(...)
建立 gRPC 伺服器的執行個體,並將此執行個體命名為grpcServer
。 - 建立指向
routeGuideServer
的指標,這是代表應用程式 API 服務的結構,並將指標命名為s.
- 使用
s.loadFeatures()
填入陣列s.savedFeatures
,其中包含可透過GetFeature
查詢的地點。 - 向 gRPC 伺服器註冊 API 服務,以便將對
GetFeature
的 RPC 呼叫,路由至適當的函式。 - 在伺服器上使用我們的連接埠詳細資料呼叫
Serve()
,以執行用戶端要求的封鎖等待作業;這項作業會持續執行,直到程序終止或呼叫Stop()
為止。
函式 loadFeatures()
會從 server/testdata.go
取得座標到位置的對應。
6. 建立用戶端
現在編輯 client/client.go
,您將在此實作用戶端程式碼。
如要呼叫遠端服務的方法,我們必須先建立 gRPC 通道,與伺服器通訊。我們將伺服器的目標 URI 字串 (在本例中,這只是位址和連接埠號碼) 傳遞至用戶端 main()
函式中的 grpc.NewClient()
,藉此建立這個項目,如下所示:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
伺服器的位址 (由變數 serverAddr
定義) 預設為 localhost:50051
,執行用戶端時,可透過指令列的 --addr
參數覆寫。
如果用戶端需要連線至需要驗證憑證的服務 (例如 TLS 或 JWT 憑證),用戶端可以將 DialOptions
物件做為參數傳遞至 grpc.NewClient
,其中包含必要憑證。RouteGuide
服務不需要任何憑證。
設定 gRPC 通道後,我們需要用戶端虛設常式,才能透過 Go 函式呼叫執行 RPC。我們使用從應用程式 .proto
檔案產生的 route_guide_grpc.pb.go
檔案提供的 NewRouteGuideClient
方法,取得該存根。
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
呼叫服務方法
在 gRPC-Go 中,RPC 會以封鎖/同步模式運作,也就是說,RPC 呼叫會等待伺服器回應,並傳回回應或錯誤。
簡單的 RPC
呼叫簡單的 RPC GetFeature
幾乎與呼叫本機方法一樣簡單,在本例中為 client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
用戶端會呼叫先前建立的 Stub 上的方法。對於方法參數,用戶端會建立並填入 Point
要求通訊協定緩衝區物件。您也會傳遞 context.Context
物件,讓我們視需要變更 RPC 的行為,例如定義呼叫的時間限制或取消進行中的 RPC。如果呼叫未傳回錯誤,用戶端可以從第一個傳回值讀取伺服器的回應資訊:
log.Println(feature)
總而言之,用戶端的 main()
函式應如下所示:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. 立即試用
在應用程式的工作目錄中執行下列指令,確認伺服器和用戶端可正常運作:
- 在一個終端機中執行伺服器:
cd server go run .
- 從另一個終端機執行用戶端:
cd client go run .
您會看到類似以下的輸出內容,為求清楚起見,時間戳記已省略:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>