之前手動實現了一次簡陋的 rpc 調用,為了簡單使用了 json 編碼信息,其實這是非常不可靠的,go 中 json 解析會有一些問題,比如整數會變成浮點數,而且 json 字符串比較占空間。
gRPC 由 google 開發,是一款語言中立、平台中立、開源的 RPC 框架,默認使用 protocol buffers 來序列化和傳輸消息,基於 http2。
protobuf
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式。
先編寫 proto 文件,再編譯成 go 文件,新建 proto/hello.proto
文件
// proto/hello.proto
syntax = "proto3";
package proto;
message String {
string value = 1; // 類型 字段 字段標識符
}
// 定義服務
service HelloService {
rpc Hello (String) returns (String);
}
安裝 protobuf 編譯器用於編譯 proto 文件,比如使用 scoop 安裝 scoop install protobuf
,或者手動下載安裝
protobuf 編譯工具可以把 proto 模板編譯成多種語言,默認不支持 go,安裝一下 go 插件
$ go get -u github.com/golang/protobuf/proto
$ go get -u github.com/golang/protobuf/protoc-gen-go
進入 hello.proto
文件目錄,編譯生成 hello.pb.go
,后續為了方便可以把編譯指令寫成 shell 腳本
$ protoc -I . --go_out=plugins=grpc:. ./hello.proto
生成的 hello.pb.go
有一些接口和方法(省略了其他的),使用時候只需要實現接口調用對應方法
type HelloServiceServer interface {
Hello(context.Context, *String) (*String, error)
}
func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
s.RegisterService(&_HelloService_serviceDesc, srv)
}
type helloServiceClient struct {
cc grpc.ClientConnInterface
}
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
return &helloServiceClient{cc}
}
func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) {
out := new(String)
err := c.cc.Invoke(ctx, "/proto.HelloService/Hello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
grpc
安裝 grpc
$ go get -u google.golang.org/grpc
服務端
在 proto 文件夾同級新建 sever/server.go
文件,實現 server 接口,注冊服務
type HelloServiceImpl struct{}
func (p *HelloServiceImpl) Hello(ctx context.Context, args *proto.String) (*proto.String, error) {
reply := &proto.String{Value: "hello " + args.GetValue()}
return reply, nil
}
func main() {
grpcServer := grpc.NewServer()
proto.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))
lis, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
grpcServer.Serve(lis)
}
客戶端
在 proto 文件夾同級新建 sever/server.go
文件,調用服務
func main() {
conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := proto.NewHelloServiceClient(conn)
reply, err := client.Hello(context.Background(), &proto.String{Value: "world"})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.GetValue())
}
驗證
首先運行服務端 go run server/server.go
,再運行客戶端
$ go run client/client.go
hello world
這里只簡單介紹了用法,關於 protobuf 的更多用法可以參考 https://developers.google.com/protocol-buffers/,關於 protobuf 的原理可以參考 https://blog.csdn.net/carson_ho/article/details/70568606,另外 grpc 還支持流式調用。
流式調用
數據傳輸是流式的,服務端和服務端不用輸完全部數據后才響應,流式傳輸可以是客戶端流式、或服務端流式,也可以同時流式傳輸,只需要一個 stream 標識
service HelloService {
rpc Hello (String) returns (String);
rpc HelloStream (String) returns (stream String); //服務端流式響應
//rpc HelloStream (stream String) returns (String); //客戶端端流式發送
//rpc HelloStream (stream String) returns (stream String); //雙向流
}
重新編譯,然后在 server/server.go
新增方法
func (HelloServiceImpl) HelloStream(args *proto.String, stream proto.HelloService_HelloStreamServer) error {
for i := 0; i < 10; i++ {
_ = stream.Send(&proto.String{Value: "hello " + args.GetValue() + strconv.Itoa(i)})
}
return nil
}
在 client/client.go
中調用 HelloStream 方法
stream, _ := client.HelloStream(context.Background(), &proto.String{Value: "World"})
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("stream.Recv: %v", err)
}
log.Printf("%s", res.String())
}
驗證,首先運行 server,然后運行 client
$ go run server/server.go
$ go run client/client.go
2020/07/21 07:41:10 value:"hello World0"
2020/07/21 07:41:10 value:"hello World1"
2020/07/21 07:41:10 value:"hello World2"
2020/07/21 07:41:10 value:"hello World3"
2020/07/21 07:41:10 value:"hello World4"
2020/07/21 07:41:10 value:"hello World5"
2020/07/21 07:41:10 value:"hello World6"
2020/07/21 07:41:10 value:"hello World7"
2020/07/21 07:41:10 value:"hello World8"
2020/07/21 07:41:10 value:"hello World9"