1. 簡介
在本程式碼研究室中,您將使用 gRPC-Go 建立用戶端和伺服器,做為以 Go 撰寫的路線對應應用程式基礎。
完成本教學課程後,您將擁有一個用戶端,可使用 gRPC 連線至遠端伺服器,取得地圖上特定座標位置的名稱或郵寄地址。完整的應用程式可能會使用這種用戶端/伺服器設計,列舉或摘要說明路線上的興趣點。
服務定義於 Protocol Buffers 檔案中,用於產生用戶端和伺服器的樣板程式碼,以便彼此通訊,節省您實作該功能的時間和精力。
這段產生的程式碼不僅會處理伺服器與用戶端之間複雜的通訊,也會處理資料序列化和還原序列化。
課程內容
- 如何使用通訊協定緩衝區定義服務 API。
- 如何使用自動程式碼生成功能,從通訊協定緩衝區定義建構以 gRPC 為基礎的用戶端和伺服器。
- 瞭解如何透過 gRPC 進行用戶端與伺服器之間的通訊。
這個程式碼研究室適合剛接觸 gRPC 的 Go 開發人員、想複習 gRPC 的使用者,以及有興趣建構分散式系統的任何人。不需要有 gRPC 相關經驗。
2. 事前準備
必要條件
請確認已安裝下列項目:
- Go 工具鍊 1.24.5 以上版本。如需安裝操作說明,請參閱 Go 的入門指南。
- 通訊協定緩衝區編譯器
protoc3.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:<>