grpc是google开发的一款rpc框架,非常好用而且流行。grpc使用proto buffer来进行接口定义,也使用proto buffer作为底层的编码格式(gRPC can use protocol buffers as both its Interface Definition Language (IDL) and as its underlying message interchange format)。
hello world
首先要安装proto buffer的编译器,这里省略。为了生成go的文件,还需要安装两个go的插件,如下:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
并且设置PATH环境变量:
$ export PATH="$PATH:$(go env GOPATH)/bin"
在项目下面创建一个proto文件夹,如图:
proto文件夹下面放的是相关结构体和rpc的定义,pkg文件夹下面放的是之后protoc自动生成的框架代码。
hello.proto的文件内容:
syntax = "proto3";
option go_package = "./hellopb";
// 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;
}
option go_package 表示的是go的import path,待会生成文件之后我们会看到有什么用。
使用以下命令生成go文件:
protoc --go_out=./pkg/ --go-grpc_out=./pkg/ proto/hello.proto
go_out和go-grpc_out参数都表示生成的文件存放的位置。
生成文件的结果:
pkg下面多了一个文件夹hellopb,里面是生成好的文件,这个hellopb就来源于我们在proto文件里面定义的option go_package = “./hellopb”;
然后分别简单地实现server和client。
server端实现了具体的rpc的处理逻辑。
package main
import (
"context"
"flag"
"fmt"
pb "learngrpc/proto/pkg/hellopb"
"log"
"net"
"google.golang.org/grpc"
)
var (
port = flag.Int("port", 8080, "The server port")
)
type server struct {
pb.UnimplementedGreeterServer
}
func (*server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "hello " + req.GetName()}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
client:
package main
import (
"context"
"flag"
"log"
"time"
pb "learngrpc/proto/pkg/hellopb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:8080", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
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.
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())
}
启动server:
启动client:
到此我们就成功完成了grpc的hello world的demo。
流式rpc (streaming rpc)
上面的hello world是一种基本的rpc用法,对于客户端来说,就像调用普通函数一样进行了rpc调用。grpc还提供了streaming RPC,可以让服务端或者客户端持续发送消息。
客户端流式 (client-side streaming RPC)
IDL:
// Accepts a stream of Points on a route being traversed, returning
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
要使用客户端流式rpc,只需要在定义时在参数前面加一个 stream。这个例子中,客户端会发送多个Point的信息,服务端汇总后再把结果返回给客户端。
客户端的实现:
// Create a random number of random points
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {
points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
stream, err := client.RecordRoute(context.Background())
if err != nil {
log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {
if err := stream.Send(point); err != nil {
log.Fatalf("%v.Send(%v) = %v", stream, point, err)
}
}
reply, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v", reply)
在调用client.RecordRoute()方法时,并不是直接进行rpc调用,而是返回一个RecordRouteClient。后面可以使用这个结构体调用Send()和CloseAndRecv()等方法。
服务端的实现:
func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
var pointCount, featureCount, distance int32
var lastPoint *pb.Point
startTime := time.Now()
for {
point, err := stream.Recv()
if err == io.EOF {
endTime := time.Now()
return stream.SendAndClose(&pb.RouteSummary{
PointCount: pointCount,
FeatureCount: featureCount,
Distance: distance,
ElapsedTime: int32(endTime.Sub(startTime).Seconds()),
})
}
if err != nil {
return err
}
pointCount++
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
featureCount++
}
}
if lastPoint != nil {
distance += calcDistance(lastPoint, point)
}
lastPoint = point
}
}
客户端在实现时,函数的参数也不是普通的参数,而是一个RecordRouteServer,它可以调用Recv()和SendAndClose()方法。
服务端流式 (Server-side streaming RPC)
IDl:
rpc ListFeatures(Rectangle) returns (stream Feature) {}
客户端的实现:
rect := &pb.Rectangle{ ... } // initialize a pb.Rectangle
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
...
}
for {
feature, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
log.Println(feature)
}
服务端的实现:
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
for _, feature := range s.savedFeatures {
if inRange(feature.Location, rect) {
if err := stream.Send(feature); err != nil {
return err
}
}
}
return nil
}
双向流式 (Bidirectional streaming RPC)
IDl:
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
客户端:
stream, err := client.RouteChat(context.Background())
waitc := make(chan struct{})
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
// read done.
close(waitc)
return
}
if err != nil {
log.Fatalf("Failed to receive a note : %v", err)
}
log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
}
}()
for _, note := range notes {
if err := stream.Send(note); err != nil {
log.Fatalf("Failed to send a note: %v", err)
}
}
stream.CloseSend()
<-waitc
服务端:
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
key := serialize(in.Location)
... // look for notes to be sent to client
for _, note := range s.routeNotes[key] {
if err := stream.Send(note); err != nil {
return err
}
}
}
}