galang 学习之grpc+ protobuf(二)

上一篇介绍了grpc+ protobuf的一个helloworld,下面介绍一个多接口,并且具有验证功能等辅助一点的例子。这个例子主要是一个坐标使用获取的例子,先看看proto文件定义:

yntax = "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 {
  //获取单个Feature方法定义
  rpc GetFeature(Point) returns (Feature) {}

  // 获取Feature列表方法定义
  rpc ListFeatures(Rectangle) returns (stream Feature) {}

  // RecordRoute记录point,客户端通过stream发送到服务端
  rpc RecordRoute(stream Point) returns (RouteSummary) {}

  // 双向stream的例子
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

// Points定义包括经度纬度
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

// 长方形定义
message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

// 位置定义,包括坐标和名称
message Feature {
  // The name of the feature.
  string name = 1;

  // The point where the feature is detected.
  Point location = 2;
}

//RouteNote包含位置和信息
message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

// RouteSummary 路程总结定义
message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}

通过命令

protoc -I routeguide/ routeguide/route_guide.proto --go_out=plugins=grpc:routeguide

生成客户端和服务端调用文件route_guide.pb.go
下面看服务端实现这些接口代码如下:

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "io"
    "io/ioutil"
    "math"
    "net"
    "time"
    "strings"
    "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"
    "path/filepath"
    "os"
    "log"
)
//支持ca认证服务端需要秘钥key以及私钥
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", "/Users/chenxy/go/src/google.golang.org/grpc/examples/route_guide/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
}

// GetFeature returns the feature at the given point.
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
}

// ListFeatures lists all features contained within the given bounding Rectangle.
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
}

// RecordRoute records a route composited of a sequence of points.
//
// It gets a stream of points, and responds with statistics about the "trip":
// number of points,  number of known features visited, total distance traveled, and
// total time spent.
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
    }
}

// RouteChat receives a stream of message/location pairs, and responds with a stream of all
// previous messages at each of those locations.
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
            }
        }
    }
}

// loadFeatures从JSON文件中加载features
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)
}

// calcDistance calculates the distance between two points using the "haversine" formula.
// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
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)
}

服务端实现了ListFeatures等这些方法,为了测试服务端需要预先从JSON文件加载Features,格式如下:

[{
    "location": {
        "latitude": 409146138,
        "longitude": -746188906
    },
    "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
}, {
    "location": {
        "latitude": 404701380,
        "longitude": -744781745
    },]

先启动服务端,客户端调用代码如下:

package main

import (
    "flag"
    "io"
    "math/rand"
    "time"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "google.golang.org/grpc/examples/route_guide/routeguide"
    "google.golang.org/grpc/grpclog"
)
//指定ca证书
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)

    // 获取一个合法的Feature
    printFeature(client, &pb.Point{Latitude: 409146138, Longitude: -746188906})

    // 这个Feature在服务端没有回返回空
    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)
}

这个例子里面涉及到多个函数接口的调用,以及stream的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳清风09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值