Golang中四種gRPC模式舉例詳解
本博客需要你有一點(diǎn)基本的gRPC的常識(shí),如果你完全是新手建議訪問(wèn)官網(wǎng)全面了解。
1. Unary RPC
proto文件如下
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (HelloRequest) returns (HelloResponse){} }
使用命令(注意命令路徑和自己的對(duì)應(yīng)):
protoc -I . --go-grpc_out=require_unimplemented_servers=false:. --go_out=. *.proto
對(duì)應(yīng)目錄上有xx.pb.go
和xx_grpc.pb.go
然后對(duì)應(yīng)目錄實(shí)現(xiàn)服務(wù)端接口
package main import ( "context" "fmt" "google.golang.org/grpc" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { resp := &service.HelloResponse{ Greeting: fmt.Sprintf("hello %s --from Golang Server", req.Name), } return resp, nil } func main() { // listen on 127.0.0.1:50051 listen, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { fmt.Println("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := grpc.NewServer() // register HelloService in grpc server service.RegisterHelloServiceServer(s, &HelloService{}) // start rpc server fmt.Println("Golang rpc server is waiting messages......") if err = s.Serve(listen); err != nil { fmt.Println("Error happened when start rpc server:", err) return } }
客戶端接口如下:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "test_grpc/service" "time" ) func main() { // connect to server conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("Connect to rpc server err:", err) return } defer conn.Close() // init service client c := service.NewHelloServiceClient(conn) // init context with timeout ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // send message req := &service.HelloRequest{Name: "Golang"} r, err := c.SayHello(ctx, req) if err != nil { fmt.Println("Send message err:", err) return } fmt.Println("Client:", r.Greeting) }
實(shí)際上為了更好得感受gRPC這種跨語(yǔ)言調(diào)用的感覺(jué),可以嘗試使用python編寫(xiě)client端代碼,直接復(fù)制proto文件,在python中使用以下命令生成對(duì)應(yīng)的proto文件(注意命令和自己的對(duì)應(yīng)):
python -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. *.proto
使用python實(shí)現(xiàn)的客戶端代碼如下:
# client template # grpc server address channel = grpc.insecure_channel("127.0.0.1:50051") stub = hello_pb2_grpc.HelloServiceStub(channel) # send request response = stub.SayHello(hello_pb2.HelloRequest(name="Python")) print(response.greeting) # hello Python --from Golang Server
2. Server-side streaming RPC
重新給出這個(gè)的proto文件,服務(wù)端將以流式數(shù)據(jù)的形式發(fā)送給客戶端數(shù)據(jù)
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (HelloRequest) returns (stream HelloResponse){} }
同理,生成對(duì)應(yīng)的proto文件后,在對(duì)應(yīng)的文件先生成server端的代碼:
package main import ( "fmt" "google.golang.org/grpc" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(req *service.HelloRequest, stream service.HelloService_SayHelloServer) error { resp := &service.HelloResponse{ Greeting: fmt.Sprintf("hello %s --from Golang Server", req.Name), } // 連續(xù)發(fā)送5次 for i := 0; i < 5; i++ { if err := stream.Send(resp); err != nil { return err } } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { fmt.Println("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := grpc.NewServer() // register HelloService in grpc server service.RegisterHelloServiceServer(s, &HelloService{}) // start rpc server fmt.Println("Golang rpc server is waiting messages......") if err = s.Serve(listen); err != nil { fmt.Println("Error happened when start rpc server:", err) return } }
同理給出客戶端的代碼:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "io" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("Connect to rpc server err:", err) return } defer conn.Close() // init service client c := service.NewHelloServiceClient(conn) // init context with timeout ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // send message req := &service.HelloRequest{Name: "Golang"} stream, err := c.SayHello(ctx, req) if err != nil { fmt.Println("Send message err:", err) return } // 加載消息 for { resp, err := stream.Recv() // 讀到結(jié)束標(biāo)志 if err == io.EOF { log.Fatalf("end.....") break } if err != nil { log.Fatalf("failed to receive response: %v", err) } log.Printf("Greeting: %s", resp.Greeting) } }
3. Client-side streaming RPC
對(duì)應(yīng)的proto文件如下
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (stream HelloRequest) returns (HelloResponse){} }
同理使用protoc命令生成對(duì)應(yīng)的proto文件,后先編寫(xiě)client端的代碼,如下:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("Connect to rpc server err:", err) return } defer conn.Close() // init service client c := service.NewHelloServiceClient(conn) // init context with timeout ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // create stream stream, err := c.SayHello(ctx) if err != nil { log.Fatalf("could not greet: %v", err) } names := []string{"World", "Gophers", "Anthropic"} for _, name := range names { // request body req := &service.HelloRequest{Name: name} if err := stream.Send(req); err != nil { log.Fatalf("faild to send request: %v", err) } } resp, err := stream.CloseAndRecv() if err != nil { log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } log.Printf("Greeting: %s", resp.Greeting) }
對(duì)應(yīng)得完成服務(wù)端的代碼:
package main import ( "fmt" "google.golang.org/grpc" "io" "net" "strings" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error { var strs []string for { msg, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } strs = append(strs, msg.Name) } resp := &service.HelloResponse{Greeting: strings.Join(strs, " ")} err := stream.SendAndClose(resp) if err != nil { return err } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { fmt.Println("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := grpc.NewServer() // register HelloService in grpc server service.RegisterHelloServiceServer(s, &HelloService{}) // start rpc server fmt.Println("Golang rpc server is waiting messages......") if err = s.Serve(listen); err != nil { fmt.Println("Error happened when start rpc server:", err) return } }
4. Bidirectional streaming RPC
新的proto文件被如下給出:
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (stream HelloRequest) returns (stream HelloResponse){} }
和上文中的操作一致,同時(shí)給出server端的代碼:
package main import ( "fmt" "google.golang.org/grpc" "io" "log" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error { for { msg, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } name := msg.Name resp := &service.HelloResponse{Greeting: name} if err = stream.Send(resp); err != nil { log.Fatalf("Failed to send a resp:%s", err) } } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { fmt.Println("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := grpc.NewServer() // register HelloService in grpc server service.RegisterHelloServiceServer(s, &HelloService{}) // start rpc server fmt.Println("Golang rpc server is waiting messages......") if err = s.Serve(listen); err != nil { fmt.Println("Error happened when start rpc server:", err) return } }
同時(shí)給出下面的client端的代碼:
package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "io" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { fmt.Println("Connect to rpc server err:", err) return } defer conn.Close() // init service client c := service.NewHelloServiceClient(conn) // init context with timeout ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // create stream stream, err := c.SayHello(ctx) if err != nil { log.Fatalf("could not greet: %v", err) } names := []string{"World", "Gophers", "Anthropic"} waitc := make(chan struct{}) go func() { for { resp, err := stream.Recv() if err == io.EOF { close(waitc) return } if err != nil { log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } log.Printf("Greeting: %s", resp.Greeting) } }() go func() { for _, name := range names { // request body req := &service.HelloRequest{Name: name} if err := stream.Send(req); err != nil { log.Fatalf("faild to send request: %v", err) } // send delay time.Sleep(1) } // 發(fā)送結(jié)束的消息 if err := stream.CloseSend(); err != nil { log.Fatalf("failed to close stream: %v", err) } }() <-waitc }
一定要注意關(guān)閉發(fā)送或者避免針對(duì)一個(gè)已經(jīng)關(guān)閉stream進(jìn)行發(fā)送消息,讀取消息是被允許的,這里有一點(diǎn)類似chan
4. ALTS
4.1 ALTS的介紹
應(yīng)用層傳輸安全(ALTS)是谷歌開(kāi)發(fā)的一種相互驗(yàn)證和傳輸加密系統(tǒng)。它用于確保谷歌基礎(chǔ)設(shè)施內(nèi) RPC 通信的安全。ALTS 類似于相互 TLS,但經(jīng)過(guò)設(shè)計(jì)和優(yōu)化,可滿足 Google 生產(chǎn)環(huán)境的需要。ALTS在gRPC中有以下的特征:
- 使用ALTS作為傳輸協(xié)議創(chuàng)建gRPC的服務(wù)端和客戶端;
- ALSTS是一個(gè)端到端的保護(hù),具有隱私性和完成性;
- 應(yīng)用可以訪問(wèn)對(duì)等信息比如對(duì)等服務(wù)賬戶;
- 支持客戶端和服務(wù)端的認(rèn)知;
- 最小的代碼更改就能使用ALTS;
值得注意的是ALTS被全部發(fā)揮作用如果應(yīng)用程序運(yùn)行在CE或者GKE中
4.2 gRPC客戶端使用ALTS傳輸安全協(xié)議
gRPC客戶端使用ALTS認(rèn)證去連接服務(wù)端,正如下面代碼中所描述的:
import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/alts" ) altsTC := alts.NewClientCreds(alts.DefaultClientOptions()) // connect to server conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(altsTC))
gRPC服務(wù)端能夠使用ALTS認(rèn)證來(lái)運(yùn)行客戶端連接到它,正如下面的描述:
import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/alts" ) altsTC := alts.NewServerCreds(alts.DefaultServerOptions()) server := grpc.NewServer(grpc.Creds(altsTC))
4.3 Server Authorization
gRPC 內(nèi)置了使用 ALTS 的服務(wù)器授權(quán)支持。使用 ALTS 的 gRPC 客戶端可以在建立連接前設(shè)置預(yù)期的服務(wù)器服務(wù)賬戶。然后,在握手結(jié)束時(shí),服務(wù)器授權(quán)會(huì)保證服務(wù)器身份與客戶端指定的服務(wù)賬戶之一相匹配。否則,連接將失敗。
import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/alts" ) clientOpts := alts.DefaultClientOptions() clientOpts.TargetServiceAccounts = []string{expectedServerSA} altsTC := alts.NewClientCreds(clientOpts) conn, err := grpc.Dial(serverAddr, grpc.WithTransportCredentials(altsTC))
總結(jié)
到此這篇關(guān)于Golang中四種gRPC模式的文章就介紹到這了,更多相關(guān)Golang中g(shù)RPC模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言題解LeetCode506相對(duì)名次示例詳解
這篇文章主要為大家介紹了go語(yǔ)言題解LeetCode506相對(duì)名次示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12golang中為什么Response.Body需要被關(guān)閉詳解
這篇文章主要給大家介紹了關(guān)于golang中為什么Response.Body需要被關(guān)閉的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08golang敏感詞過(guò)濾的實(shí)現(xiàn)
本文主要介紹了golang敏感詞過(guò)濾的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01GO語(yǔ)言協(xié)程創(chuàng)建使用并通過(guò)channel解決資源競(jìng)爭(zhēng)
這篇文章主要為大家介紹了GO語(yǔ)言協(xié)程創(chuàng)建使用并通過(guò)channel解決資源競(jìng)爭(zhēng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04Go中阻塞以及非阻塞操作實(shí)現(xiàn)(Goroutine和main Goroutine)
本文主要介紹了Go中阻塞以及非阻塞操作實(shí)現(xiàn)(Goroutine和main Goroutine),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05利用Golang實(shí)現(xiàn)TCP連接的雙向拷貝詳解
公司中遇到了一個(gè)使用golang編寫(xiě)的agent程序,所以這篇文章主要給大家介紹了關(guān)于利用Go如何實(shí)現(xiàn)TCP連接的雙向拷貝的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考,下面隨著小編來(lái)一起看看吧。2017-09-09golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)
本文主要介紹了golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05解決Golang小數(shù)float64在實(shí)際工程中加減乘除的精度問(wèn)題
這篇文章主要介紹了解決Golang小數(shù)float64在實(shí)際工程中加減乘除的精度問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03詳解Go語(yǔ)言中用 os/exec 執(zhí)行命令的五種方法
這篇文章主要介紹了Go語(yǔ)言中用 os/exec 執(zhí)行命令的五種方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11