gRPC是什麼
官方介紹:
https://grpc.io/docs/what-is-grpc/introduction/
“A high-performance, open-source universal RPC framework”
- 多語言:語言中立,支援多種語言。
- 輕量級、高性能:序列化支援 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高性能序列化架構。
- IDL:基于檔案定義服務,通過 proto3 工具生成指定語言的資料結構、服務端接口以及用戶端 Stub。
- 設計理念
- 移動端:基于标準的 HTTP2 設計,支援雙向流、消息頭壓縮、單 TCP 的多路複用、服務端推送等特性,這些特性使得 gRPC 在移動端裝置上更加省電和節省網絡流量。
- 服務而非對象、消息而非引用:促進微服務的系統間粗粒度消息互動設計理念。
- 負載無關的:不同的服務需要使用不同的消息類型和編碼,例如 protocol buffers、JSON、XML 和 Thrift。
- 流:Streaming API。
- 阻塞式和非阻塞式:支援異步和同步處理在用戶端和服務端間互動的消息序列。
- 中繼資料交換:常見的橫切關注點,如認證或跟蹤,依賴資料交換。
- 标準化狀态碼:用戶端通常以有限的方式響應 API 調用傳回的錯誤。
小結
- grpc是個協定,對應的是proto檔案
- protobuf 是将jrpc轉化為代碼的工具
安裝
grpc包
go get -u google.golang.org/grpc
protobuf
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
protoc文法
- -I 參數:指定import路徑,可以指定多個-I參數,編譯時按順序查找,不指定時預設查找目前目錄
- --go_out :golang編譯支援,支援以下參數
- plugins=plugin1+plugin2 - 指定插件,目前隻支援grpc,即:plugins=grpc
- M 參數 - 指定導入的.proto檔案路徑編譯後對應的golang包名(不指定本參數預設就是.proto檔案中import語句的路徑)
- import_prefix=xxx - 為所有import路徑添加字首,主要用于編譯子目錄内的多個proto檔案,這個參數按理說很有用,尤其适用替代一些情況時的M參數,但是實際使用時有個蛋疼的問題導緻并不能達到我們預想的效果,自己嘗試看看吧
- import_path=foo/bar - 用于指定未聲明package或go_package的檔案的包名,最右面的斜線前的字元會被忽略
- 末尾 :編譯檔案路徑 .proto檔案路徑(支援通配符
protoc --go_out=. example.proto
protoc --go-grpc_out=. example.proto
demo
demo git位址

proto檔案
syntax = "proto3";
# 定義了包名
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
生成
protoc --go_out=. helloworld.proto
protoc --go-grpc_out=. helloworld.proto
會多了兩個檔案
服務端
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
注意
服務端其實是實作協定中的接口,即實作所有方法
type HelloServer interface {
// 定義SayHello方法
SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
mustEmbedUnimplementedHelloServer()
}
很多教程實作了mustEmbedUnimplementedHelloServer 這個方法,但是由于是小寫, 同目錄下是好的,跨了目錄就會有問題。
應該直接:
type server struct {
pb.UnimplementedGreeterServer
}
用戶端
package main
import (
"context"
"log"
"os"
"time"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
常見的坑
- grpc版本和protoc的版本不一緻,如果是第一次用,就都用最新的就好了,後面就一直用這個版本。或者換最新版本重新生成代碼檔案。
結語
- 如果有不對的地方歡迎指正。
- 如果有不了解的地方歡迎指出我來加栗子。
- 如果感覺OK可以點贊讓更多人看到它。