1. Wprowadzenie
W tym module nauczysz się tworzyć klienta i serwer za pomocą gRPC-Go, które będą stanowić podstawę aplikacji do mapowania tras napisanej w Go.
Po ukończeniu tego samouczka będziesz mieć klienta, który łączy się z serwerem zdalnym za pomocą gRPC, aby uzyskać nazwę lub adres pocztowy miejsca znajdującego się pod określonymi współrzędnymi na mapie. W pełni funkcjonalna aplikacja może korzystać z tego modelu klient-serwer, aby wyliczać lub podsumowywać ważne miejsca na trasie.
Usługa jest zdefiniowana w pliku Protocol Buffers, który będzie używany do generowania kodu szablonowego dla klienta i serwera, aby mogły się ze sobą komunikować. Dzięki temu zaoszczędzisz czas i wysiłek potrzebny na wdrożenie tej funkcji.
Wygenerowany kod obsługuje nie tylko złożoność komunikacji między serwerem a klientem, ale także serializację i deserializację danych.
Czego się nauczysz
- Jak używać buforów protokołu do definiowania interfejsu API usługi.
- Jak utworzyć klienta i serwer oparte na gRPC na podstawie definicji Protocol Buffers za pomocą automatycznego generowania kodu.
- Znajomość komunikacji klient-serwer z użyciem gRPC.
Te warsztaty są przeznaczone dla programistów Go, którzy dopiero zaczynają korzystać z gRPC lub chcą sobie przypomnieć podstawy tej technologii, a także dla wszystkich innych osób zainteresowanych tworzeniem systemów rozproszonych. Nie musisz mieć wcześniejszego doświadczenia z gRPC.
2. Zanim zaczniesz
Wymagania wstępne
Sprawdź, czy masz zainstalowane te elementy:
- Narzędzia Go w wersji 1.24.5 lub nowszej. Instrukcje instalacji znajdziesz w przewodniku dla początkujących dotyczącym języka Go.
- kompilator bufora protokołu
protoc
w wersji 3.27.1 lub nowszej; Instrukcje instalacji znajdziesz w przewodniku instalacji kompilatora. - Wtyczki kompilatora bufora protokołu dla Go i gRPC. Aby zainstalować te wtyczki, uruchom te polecenia:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Zaktualizuj zmienną PATH
, aby kompilator bufora protokołu mógł znaleźć wtyczki:
export PATH="$PATH:$(go env GOPATH)/bin"
Pobierz kod
Aby nie trzeba było zaczynać od zera, w tym ćwiczeniu znajdziesz szkielet kodu źródłowego aplikacji, który możesz uzupełnić. Z tych instrukcji dowiesz się, jak dokończyć aplikację, w tym jak użyć wtyczek kompilatora buforów protokołów do wygenerowania kodu gRPC.
Pobierz ten kod źródłowy jako archiwum ZIP z GitHub i rozpakuj jego zawartość.
Jeśli nie chcesz wpisywać implementacji, gotowy kod źródłowy jest dostępny na GitHubie.
3. Określ usługę
Pierwszym krokiem jest zdefiniowanie usługi gRPC aplikacji, jej metody RPC oraz typów wiadomości żądania i odpowiedzi za pomocą buforów protokołu. Twoja usługa będzie zapewniać:
- Metoda RPC o nazwie
GetFeature
, która jest zaimplementowana na serwerze i wywoływana przez klienta. - Typy wiadomości
Point
iFeature
to struktury danych wymieniane między klientem a serwerem podczas korzystania z metodyGetFeature
. Klient podaje współrzędne mapy jakoPoint
w żądaniuGetFeature
wysyłanym do serwera, a serwer odpowiada, przesyłając odpowiedniFeature
, który opisuje wszystko, co znajduje się pod tymi współrzędnymi.
Ta metoda RPC i jej typy wiadomości będą zdefiniowane w pliku routeguide/route_guide.proto
podanego kodu źródłowego.
Protokoły buforów są powszechnie znane jako protobufy. Więcej informacji o terminologii gRPC znajdziesz w artykule Podstawowe koncepcje, architektura i cykl życia.
Rodzaje wiadomości
W routeguide/route_guide.proto
pliku kodu źródłowego najpierw zdefiniuj Point
typ wiadomości. Symbol Point
reprezentuje parę współrzędnych szerokości i długości geograficznej na mapie. W tym ćwiczeniu używaj liczb całkowitych jako współrzędnych:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Numery 1
i 2
to unikalne identyfikatory poszczególnych pól w strukturze message
.
Następnie określ Feature
typ wiadomości. Feature
używa pola string
na nazwę lub adres pocztowy czegoś w lokalizacji określonej przez Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Metoda usługi
Plik route_guide.proto
ma strukturę service
o nazwie RouteGuide
, która definiuje co najmniej 1 metodę udostępnianą przez usługę aplikacji.
Dodaj metodę rpc
GetFeature
w definicji RouteGuide
. Jak wspomnieliśmy wcześniej, ta metoda wyszukuje nazwę lub adres lokalizacji na podstawie podanego zestawu współrzędnych, więc w przypadku danego Point
zwraca GetFeature
Feature
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Jest to metoda RPC typu unary: prosta procedura RPC, w której klient wysyła żądanie do serwera i czeka na odpowiedź, podobnie jak w przypadku lokalnego wywołania funkcji.
4. Generowanie kodu klienta i serwera
Następnie wygeneruj kod gRPC dla klienta i serwera z pliku .proto
za pomocą kompilatora bufora protokołu. W katalogu routeguide
uruchom:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
To polecenie generuje te pliki:
route_guide.pb.go
, który zawiera funkcje tworzenia typów wiadomości aplikacji i uzyskiwania dostępu do ich danych.route_guide_grpc.pb.go
, który zawiera funkcje używane przez klienta do wywoływania zdalnej metody gRPC usługi oraz funkcje używane przez serwer do udostępniania tej zdalnej usługi.
Następnie zaimplementujemy metodę GetFeature
po stronie serwera, aby gdy klient wyśle żądanie, serwer mógł odpowiedzieć.
5. Wdrażanie usługi
Funkcja GetFeature
po stronie serwera wykonuje główną pracę: pobiera wiadomość Point
od klienta i zwraca w wiadomości Feature
odpowiednie informacje o lokalizacji z listy znanych miejsc. Oto implementacja funkcji w języku 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
}
Gdy ta metoda jest wywoływana w odpowiedzi na żądanie klienta zdalnego, funkcja otrzymuje obiekt Context
opisujący wywołanie RPC oraz obiekt Point
protokołu buforowania z tego żądania klienta. Funkcja zwraca obiekt bufora protokołu Feature
dla wyszukiwanej lokalizacji i w razie potrzeby obiekt error
.
W metodzie wypełnij obiekt Feature
odpowiednimi informacjami dla danego obiektu Point
, a następnie return
go wraz z błędem nil
, aby poinformować gRPC, że zakończono obsługę RPC i że obiekt Feature
można zwrócić do klienta.
Metoda GetFeature
wymaga utworzenia i zarejestrowania obiektu routeGuideServer
, aby żądania klientów dotyczące wyszukiwania lokalizacji mogły być kierowane do tej funkcji. Możesz to zrobić w 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)
}
Oto, co dzieje się w main()
krok po kroku:
- Określ port TCP, który ma być używany do nasłuchiwania żądań klientów zdalnych, używając
lis, err := net.Listen(...)
. Domyślnie aplikacja używa portu TCP50051
określonego przez zmiennąport
lub przez przekazanie przełącznika--port
w wierszu poleceń podczas uruchamiania serwera. Jeśli nie można otworzyć portu TCP, aplikacja kończy się błędem krytycznym. - Utwórz instancję serwera gRPC za pomocą
grpc.NewServer(...)
i nadaj jej nazwęgrpcServer
. - Utwórz wskaźnik do
routeGuideServer
, struktury reprezentującej usługę API aplikacji, i nadaj mu nazwęs.
. - Użyj
s.loadFeatures()
, aby wypełnić tablicęs.savedFeatures
lokalizacjami, które można wyszukać za pomocąGetFeature
. - Zarejestruj usługę API na serwerze gRPC, aby wywołania RPC do
GetFeature
były kierowane do odpowiedniej funkcji. - Wywołaj funkcję
Serve()
na serwerze, podając szczegóły portu, aby zablokować oczekiwanie na żądania klienta. Będzie to trwało do momentu zakończenia procesu lub wywołania funkcjiStop()
.
Funkcja loadFeatures()
pobiera mapowania współrzędnych na lokalizacje z server/testdata.go
.
6. Tworzenie klienta
Teraz edytuj plik client/client.go
, w którym zaimplementujesz kod klienta.
Aby wywołać metody usługi zdalnej, musimy najpierw utworzyć kanał gRPC do komunikacji z serwerem. Tworzymy go, przekazując ciąg URI serwera docelowego (w tym przypadku jest to po prostu adres i numer portu) do funkcji grpc.NewClient()
w funkcji main()
klienta w ten sposób:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
Adres serwera, zdefiniowany przez zmienną serverAddr
, domyślnie ma wartość localhost:50051
i może zostać zastąpiony przełącznikiem --addr
w wierszu poleceń podczas uruchamiania klienta.
Jeśli klient musi połączyć się z usługą, która wymaga danych logowania, np. danych logowania TLS lub JWT, może przekazać obiekt DialOptions
jako parametr do grpc.NewClient
, który zawiera wymagane dane logowania. Usługa RouteGuide
nie wymaga żadnych danych logowania.
Po skonfigurowaniu kanału gRPC potrzebujemy namiastek klienta, aby wykonywać wywołania RPC za pomocą wywołań funkcji Go. Stub uzyskujemy za pomocą metody NewRouteGuideClient
udostępnianej przez plik route_guide_grpc.pb.go
wygenerowany z pliku .proto
aplikacji.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Wywoływanie metod usługi
W gRPC-Go wywołania RPC działają w trybie blokującym/synchronicznym, co oznacza, że wywołanie RPC czeka na odpowiedź serwera i zwraca odpowiedź lub błąd.
Simple RPC
Wywołanie prostego wywołania RPC GetFeature
jest prawie tak proste jak wywołanie metody lokalnej, w tym przypadku 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)
}
Klient wywołuje metodę w utworzonym wcześniej module pośredniczącym. W przypadku parametrów metody klient tworzy i wypełnia obiekt bufora protokołu żądania Point
. Przekazujesz też obiekt context.Context
, który w razie potrzeby pozwala nam zmienić działanie RPC, np. określić limit czasu wywołania lub anulować RPC w trakcie wykonywania. Jeśli wywołanie nie zwróci błędu, klient może odczytać informacje o odpowiedzi z serwera z pierwszej wartości zwracanej:
log.Println(feature)
Funkcja main()
klienta powinna wyglądać tak:
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. Wypróbuj
Aby sprawdzić, czy serwer i klient działają prawidłowo, wykonaj te polecenia w katalogu roboczym aplikacji:
- Uruchom serwer w jednym terminalu:
cd server go run .
- Uruchom klienta w innym terminalu:
cd client go run .
Zobaczysz dane wyjściowe podobne do tych (sygnatury czasowe zostały pominięte dla przejrzystości):
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:<>
8. Co dalej?
- Dowiedz się, jak działa gRPC, z artykułów Wprowadzenie do gRPC i Podstawowe pojęcia.
- Zapoznaj się z samouczkiem dotyczącym podstaw.
- Zapoznaj się z dokumentacją interfejsu API