gRPC是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
》原文出自《 》
1.安装protobuf
去到下载最新版本,自行编译,在加入到环境变量中,用于生成proto代码.
windows可以直接在这里编译好的可执行文件.
检测是否安装成功
protoc --versionlibprotoc 3.1.0
使用go语言还需要下载golang protobuf和代码生成的plugin工具
go get -u github.com/golang/protobuf/proto // golang protobuf 库go get -u github.com/golang/protobuf/protoc-gen-go //protoc --go_out 工具
安装grpc-go
go get github.com/grpc/grpc-go
在对应的example目录有对应的示例程序。
以route_guide为例,首先看PB描述文件
syntax = "proto3";option java_multiple_files = true;option java_package = "io.grpc.examples.routeguide";option java_outer_classname = "RouteGuideProto";package routeguide;// Interface exported by the server.service RouteGuide { // 类似普通的函数调用,客户端发送请求Point到服务器,服务器返回相应Feature. rpc GetFeature(Point) returns (Feature) {} // 客户端发起一次请求,服务器端返回一个流式数据,比如一个数组中的逐个元素 rpc ListFeatures(Rectangle) returns (stream Feature) {} // 客户端发起的请求是一个流式的数据,比如数组中的逐个元素,服务器返回一个相应 rpc RecordRoute(stream Point) returns (RouteSummary) {} // 客户端发起的请求是一个流式数据,比如数组中的逐个元素,二服务器返回的也是一个类似的数据结构 rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}}// message用于描述一个消息类型,可以看做一个对象message Point { // 字段类型 字段名称 int32 latitude = 1; int32 longitude = 2;}message Rectangle { Point lo = 1; Point hi = 2;}message Feature { string name = 1; Point location = 2;}message RouteNote { Point location = 1; string message = 2;}message RouteSummary { // 字段命名默认会转换为驼峰方式 如 pointCount int32 point_count = 1; int32 feature_count = 2; int32 distance = 3; int32 elapsed_time = 4;}
使用protoc命令生成相关文件:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto--proto_path:代表PB描述文件的目录,多个目录可使用多次引入,可简写为-I=IMPORT_PATH当PB描述文件中import了其他的PB文件时,可用该方式指定导入的路径--cpp_out:c++文件生成的目标目录--java_out:java文件生成的目标目录--python_out:python文件生成的目标目录--go_out:go文件生成的目标目录--ruby_out:ruby文件生成的目标目录--php_out:php文件生成的目标目录protoc3 --go_out=plugins=grpc:go protos\route_guide.proto
生成对应的pb.go文件。这里用了plugins选项,提供对grpc的支持,否则不会生成Service的接口。
java也有对应的plugin支持,可以直接生成Service接口等内容,具体可。
生成接口后,剩下的值需要server端完成实现即可。
服务端程序:
package mainimport ( "encoding/json" "flag" "fmt" "io" "io/ioutil" "math" "net" "time" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "github.com/golang/protobuf/proto" pb "google.golang.org/grpc/examples/route_guide/routeguide")var ( tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") certFile = flag.String("cert_file", "../testdata/server1.pem", "The TLS cert file") keyFile = flag.String("key_file", "../testdata/server1.key", "The TLS key file") jsonDBFile = flag.String("json_db_file", "../testdata/route_guide_db.json", "A json file containing a list of features") port = flag.Int("port", 10000, "The server port"))type routeGuideServer struct { savedFeatures []*pb.Feature routeNotes map[string][]*pb.RouteNote}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}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}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 }}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) if _, present := s.routeNotes[key]; !present { s.routeNotes[key] = []*pb.RouteNote{in} } else { s.routeNotes[key] = append(s.routeNotes[key], in) } for _, note := range s.routeNotes[key] { if err := stream.Send(note); err != nil { return err } } }}func (s *routeGuideServer) loadFeatures(filePath string) { file, err := ioutil.ReadFile(filePath) if err != nil { grpclog.Fatalf("Failed to load default features: %v", err) } if err := json.Unmarshal(file, &s.savedFeatures); err != nil { grpclog.Fatalf("Failed to load default features: %v", err) }}func toRadians(num float64) float64 { return num * math.Pi / float64(180)}func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 { const CordFactor float64 = 1e7 const R float64 = float64(6371000) // metres lat1 := float64(p1.Latitude) / CordFactor lat2 := float64(p2.Latitude) / CordFactor lng1 := float64(p1.Longitude) / CordFactor lng2 := float64(p2.Longitude) / CordFactor φ1 := toRadians(lat1) φ2 := toRadians(lat2) Δφ := toRadians(lat2 - lat1) Δλ := toRadians(lng2 - lng1) a := math.Sin(Δφ/2)*math.Sin(Δφ/2) + math.Cos(φ1)*math.Cos(φ2)* math.Sin(Δλ/2)*math.Sin(Δλ/2) c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) distance := R * c return int32(distance)}func inRange(point *pb.Point, rect *pb.Rectangle) bool { left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) if float64(point.Longitude) >= left && float64(point.Longitude) <= right && float64(point.Latitude) >= bottom && float64(point.Latitude) <= top { return true } return false}func serialize(point *pb.Point) string { return fmt.Sprintf("%d %d", point.Latitude, point.Longitude)}func newServer() *routeGuideServer { s := new(routeGuideServer) s.loadFeatures(*jsonDBFile) s.routeNotes = make(map[string][]*pb.RouteNote) return s}func main() { flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { grpclog.Fatalf("failed to listen: %v", err) } var opts []grpc.ServerOption if *tls { creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) } opts = []grpc.ServerOption{grpc.Creds(creds)} } grpcServer := grpc.NewServer(opts...) pb.RegisterRouteGuideServer(grpcServer, newServer()) grpcServer.Serve(lis)}
客户端程序:
package mainimport ( "flag" "io" "math/rand" "time" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" pb "github.com/grpc/grpc-go/examples/route_guide/routeguide" "google.golang.org/grpc/grpclog")var ( tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") caFile = flag.String("ca_file", "testdata/ca.pem", "The file containning the CA root cert file") serverAddr = flag.String("server_addr", "127.0.0.1:10000", "The server address in the format of host:port") serverHostOverride = flag.String("server_host_override", "x.test.youtube.com", "The server name use to verify the hostname returned by TLS handshake"))// printFeature gets the feature for the given point.func printFeature(client pb.RouteGuideClient, point *pb.Point) { grpclog.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude) feature, err := client.GetFeature(context.Background(), point) if err != nil { grpclog.Fatalf("%v.GetFeatures(_) = _, %v: ", client, err) } grpclog.Println(feature)}// printFeatures lists all the features within the given bounding Rectangle.func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) { grpclog.Printf("Looking for features within %v", rect) stream, err := client.ListFeatures(context.Background(), rect) if err != nil { grpclog.Fatalf("%v.ListFeatures(_) = _, %v", client, err) } for { feature, err := stream.Recv() if err == io.EOF { break } if err != nil { grpclog.Fatalf("%v.ListFeatures(_) = _, %v", client, err) } grpclog.Println(feature) }}// runRecordRoute sends a sequence of points to server and expects to get a RouteSummary from server.func runRecordRoute(client pb.RouteGuideClient) { // 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)) } grpclog.Printf("Traversing %d points.", len(points)) stream, err := client.RecordRoute(context.Background()) if err != nil { grpclog.Fatalf("%v.RecordRoute(_) = _, %v", client, err) } for _, point := range points { if err := stream.Send(point); err != nil { grpclog.Fatalf("%v.Send(%v) = %v", stream, point, err) } } reply, err := stream.CloseAndRecv() if err != nil { grpclog.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } grpclog.Printf("Route summary: %v", reply)}// runRouteChat receives a sequence of route notes, while sending notes for various locations.func runRouteChat(client pb.RouteGuideClient) { notes := []*pb.RouteNote{ {&pb.Point{Latitude: 0, Longitude: 1}, "First message"}, {&pb.Point{Latitude: 0, Longitude: 2}, "Second message"}, {&pb.Point{Latitude: 0, Longitude: 3}, "Third message"}, {&pb.Point{Latitude: 0, Longitude: 1}, "Fourth message"}, {&pb.Point{Latitude: 0, Longitude: 2}, "Fifth message"}, {&pb.Point{Latitude: 0, Longitude: 3}, "Sixth message"}, } stream, err := client.RouteChat(context.Background()) if err != nil { grpclog.Fatalf("%v.RouteChat(_) = _, %v", client, err) } waitc := make(chan struct{}) go func() { for { in, err := stream.Recv() if err == io.EOF { // read done. close(waitc) return } if err != nil { grpclog.Fatalf("Failed to receive a note : %v", err) } grpclog.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 { grpclog.Fatalf("Failed to send a note: %v", err) } } stream.CloseSend() <-waitc}func randomPoint(r *rand.Rand) *pb.Point { lat := (r.Int31n(180) - 90) * 1e7 long := (r.Int31n(360) - 180) * 1e7 return &pb.Point{Latitude: lat, Longitude: long}}func main() { flag.Parse() var opts []grpc.DialOption if *tls { var sn string if *serverHostOverride != "" { sn = *serverHostOverride } var creds credentials.TransportCredentials if *caFile != "" { var err error creds, err = credentials.NewClientTLSFromFile(*caFile, sn) if err != nil { grpclog.Fatalf("Failed to create TLS credentials %v", err) } } else { creds = credentials.NewClientTLSFromCert(nil, sn) } opts = append(opts, grpc.WithTransportCredentials(creds)) } else { opts = append(opts, grpc.WithInsecure()) } conn, err := grpc.Dial(*serverAddr, opts...) if err != nil { grpclog.Fatalf("fail to dial: %v", err) } defer conn.Close() client := pb.NewRouteGuideClient(conn) // Looking for a valid feature printFeature(client, &pb.Point{Latitude: 409146138, Longitude: -746188906}) // Feature missing. printFeature(client, &pb.Point{Latitude: 0, Longitude: 0}) // Looking for features between 40, -75 and 42, -73. printFeatures(client, &pb.Rectangle{ Lo: &pb.Point{Latitude: 400000000, Longitude: -750000000}, Hi: &pb.Point{Latitude: 420000000, Longitude: -730000000}, }) // RecordRoute runRecordRoute(client) // RouteChat runRouteChat(client)}