Building grpc service with golang

Time:2020-4-6

Building grpc service with golang
This tutorial provides a basic tutorial for go to use grpc

In the tutorial you will learn how to:

  • stay.protoA service is defined in the file.
  • Use the protocol buffer compiler to generate client and server code.
  • Use grpc’s go API to write a client and server for your service.

Before you continue, make sure you have a good understanding of the grpc concept and are familiar with the protocol buffer. Note that the examples in the tutorial useproto3Version of protocol buffer: you can learn more about it in the protobuf language guide and protobuf code generation guide.

Why grpc

Our example is a simple road map application. The client can obtain route feature information, create their route summary, and exchange route information such as traffic status update with the server or other clients.

With grpc, we can.protoOur services are defined in the file, and clients and servers are implemented in any language supported by grpc. Clients and servers can run in various environments from server to your own tablet – grpc will also solve the complexity of communication between all different languages and environments for you. We also get all the advantages of using protocol buffer, including effective serialization (more efficient than JSON in speed and volume), simple IDL (interface definition language) and easy interface update.

install

Install grpc package

First of all, you need to install the grpc golang version of the package. At the same time, theexamplesThe directory contains the code for the example roadmap application in the tutorial.

$ go get google.golang.org/grpc

Then switch to`grpc-go/examples/route_guide: Directory:

$ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide

Install related tools and plug-ins

  • Install the protocol buffer compiler

The easiest way to install the compiler is to download the precompiled protoc binary file from https://github.com/protocolbu… And the compiler binary file corresponding to each platform can be found in the warehouse. Here we useMac OsFor example, download and extract the file from https://github.com/protocolbu.

To updatePATHSystem variables, or make sureprotocPut it in.PATHThe included directory is already in.

  • Installing the protoc compiler plug-in
$ go get -u github.com/golang/protobuf/protoc-gen-go

Compiler plug inprotoc-gen-goWill be installed in$GOBIN, default at$GOPATH/bin。 CompilerprotocMust be$PATHIt can be found in:

$ export PATH=$PATH:$GOPATH/bin

Defining services

The first step is to use the protocol buffer to define grpc service and method request and response types. You can download the sample codeexamples/route_guide/routeguide/route_guide.protoSee the complete.protoPapers.

To define a service, you need to.protoA namedservice

service RouteGuide {
   ...
}

Then define it in the service definitionrpcMethod, specifying their request and response types. Grpc allows you to define four types of service methods, all of which will be applied to ourRouteGuideIn service.

  • In a simple RPC, the client sends the request to the server using the stub and waits for the response to return, just like a normal function call.
//Get features for a given location
rpc GetFeature(Point) returns (Feature) {}
  • In server-side streaming RPC, the client sends a request to the server and gets the stream to read back a series of messages. The client reads from the returned stream until there are no more messages. As our example shows, you can specify the server-side flow method by putting the < font color = “red” > stream < / font > keyword before the response type.
//Gets the characteristics available in a given rectangle. The result is
//Streaming instead of returning immediately
//Because rectangles can cover large areas and contain a large number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
  • Client stream RPC, where the client writes a series of messages using the stream provided by grpc and sends them to the server. When the client finishes writing the message, it waits for the server to read all the messages and return its response. You can specify the client stream method by placing the < font color = “red” > stream < / font > keyword before the request type.
//A series of points crossed on a receiving route when the journey is over
//The server will return a message of type routesummary
rpc RecordRoute(stream Point) returns (RouteSummary) {}
  • Two way flow RPC, both sides use read-write flow to send a series of messages. The two streams run independently, so the client and the server can read and write in the order they like: for example, the server can wait to receive all the client messages before writing a response, or can read the messages before writing them, or some other read-write combination. The order of messages in each flow is preserved. You can specify this type of method by placing the < font color = “red” > stream < / font > keyword before both the request and the response.
//Receive a series of routenotes type messages sent from the route, and also receive other routenotes (for example, from other users)
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

Ours.protoProtocol buffer message type definitions for all request and response types are also required in the file. Like the followingPointMessage type:

//Points are represented as longitude latitude pairs in the E7 representation.
//(degree multiplied by 10 * * 7 and rounded to the nearest whole number).
//Latitude should be within + / - 90 degrees and longitude should be within
//Range + / - 180 degrees (included)
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

Generate client and server code

Next, from our.protoThe service definition generates the interface between the grpc client and the server. We useprotocThe compiler and the compiler plug-ins installed above do this:

In the exampleroute_guideRun in:

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

After running the command, theroute_guideDirectoryrouteguideGenerate under directoryroute_guide.pb.goPapers.

pb.goThe file contains:

  • All protocol buffer code used to populate, serialize, and retrieve the request and response message types we defined.
  • A client stub for the client to callRouteGuideMethod defined in the service.
  • An interface type to be implemented by the serverRouteGuideServer, the interface type containsRouteGuideAll methods defined in the service.

Create grpc server

First let’s see how to createRouteGuideThe server. There are two ways to make ourRouteGuideService work:

  • Implement the service interface we generate from the service definition: do what the service actually does.
  • Run a grpc server to listen for requests from clients and then distribute them to the correct service implementation.

You can find the implementation code of the route guide service in our example in grpc-go / examples / route_guide / server / server.go of the GPRC package you just installed. Let’s see how he works.

Implement routeguide

As you can see, there is one in the implementation coderouteGuideServerStructure type, which implementsprotocCompiler generatedpb.goDefined in the fileRouteGuideServerInterface.

type routeGuideServer struct {
        ...
}
...

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
        ...
}
...

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}
...

Ordinary PRC

routeGuideServerImplement all of our service methods. First, let’s look at the simplest typesGetFeature, it just gets one from the clientPointAnd from itsFeatureThe correspondingFeatureInformation.

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{"", point}, nil
}

This method passes the RPC context object and the client’sPointProtocol buffer request message, which returns aFeatureProtocol buffer messages and errors of type. In this method, we use the appropriate information to fill inFeature, then return it and returnnilError to inform grpc that we have finished processing RPC and can return ‘feature’ to the client.

Service end streaming RPC

Now, let’s look at a streaming RPC in the service method.ListFeaturesIt’s server-side streaming RPC, so we need toFeatureSend back to the client.

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
}

As you can see, this time we didn’t get a simple request and response object, but a request object (in which the client needs to findFeatureOfRectangle)And a specialRouteGuide_ListFeaturesServeR object to write the response.

In this method, we fill in all the items that need to be returnedFeatureObject and use theSend()Method to write themRouteGuide_ListFeaturesServer。 Finally, as in a simple RPC, we returnnilError to tell grpc that we have finished writing the response. If any error occurs in this call, we will return a nonnilError; grpc layer will convert it to the appropriate RPC state to send online.

Client streaming RPC

Now, let’s look at something more complex: the client flow methodRecordRoute, get the point stream from the client, and return aRouteSummary。 As you can see, this time there is no wayrequestParameters. Instead, it gets aRouteGuide_RecordRouteServerStream that the server can use to read and write messages – it can useRecv()Method to receive client messages and use theSendAndClose()Method returns its single response.

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
    }
}

In the method body, we useRouteGuide_RecordRouteServerOfRecv()Method constantly reads the client’s request into a request object (in this casePoint)Until there are no more messages: the server needs to check theRecv()Error returned. If sonilIf it is io.eof, it means that the message flow has ended and the server can return its routesummary. If the error is a different value, we will return the error “as is” so that the grpc layer can transition it to RPC state.

Bidirectional flow RPC

Finally, let’s look at the two-way flow RPC methodRouteChat()

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)

        s.mu.Lock()
        s.routeNotes[key] = append(s.routeNotes[key], in)
        // Note: this copy prevents blocking other clients while serving this one.
        // We don't need to do a deep copy, because elements in the slice are
        // insert-only and never modified.
        rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
        copy(rn, s.routeNotes[key])
        s.mu.Unlock()

        for _, note := range rn {
            if err := stream.Send(note); err != nil {
                return err
            }
        }
    }
}

This time, we got oneRouteGuide_RouteChatServerStream, as in the client stream example, which can be used to read and write messages. However, this time, when the client is still writing messages to its message flow, we will write the messages to be returned to the flow.

The read-write syntax here is very similar to our client streaming method, except that the server uses streamingSend()Method, notSendAndClose()Because the server writes multiple responses. Although both sides will always get each other’s messages according to the other’s write order, both the client and the server can read and write in any order – the stream runs completely independently (that is, the server can write the stream after receiving the request, or can receive a request to write a response). The same client can send a request to read a response after writing the request.)

Start server

Once all the methods are implemented, we also need to start the grpc server so that clients can actually use our services. The following code snippet shows how to startRouteGuideService.

flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
        log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})
... // determine whether to use TLS
grpcServer.Serve(lis)

In order to build and start the server, we need to:

  • Specify the interface to listen for client requestslis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))。
  • Usegrpc.NewServer()Create an instance of grpc server.
  • Register our service implementation with grpc server.
  • Call on the server using our port detailsServe()Block wait until the process is killed or calledStop()Until.

Create client

In this section we willRouteGuideThe service creates the go client. You can see the complete client code in grpc-go / examples / route_guide / client / client.go.

Creating stub

To call the service’s methods, we first need to create a grpc channel to communicate with the server. We pass the server address and port number togrpc.Dial()To create a channel, as follows:

conn, err := grpc.Dial(*serverAddr)
if err != nil {
    ...
}
defer conn.Close()

If the service you requested requires authentication, you cangrpc.DialUse < font color = “red” >DialOptionsSet authentication voucher (such as TLS, GCE voucher, JWT voucher) – but ourRouteGuideServices do not need these.

After setting up the grpc channel, we need a client stub to execute the RPC. We use the.protoGeneratedpbProvided in the packageNewRouteGuideClientMethod to get the client stub.

client := pb.NewRouteGuideClient(conn)

Generatedpb.goThe file defines the client interface typeRouteGuideClientThe method in the interface is implemented with the structure type of the client stub, so the client stub obtained above is usedclientYou can directly call the methods listed in the following interface types.

type RouteGuideClient interface {
    GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error)

    ListFeatures(ctx context.Context, in *Rectangle, opts ...grpc.CallOption) (RouteGuide_ListFeaturesClient, error)

    RecordRoute(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RecordRouteClient, error)
    RouteChat(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RouteChatClient, error)
}

Each implementation method will then request the corresponding method of grpc server to obtain the response of the server, such as:

func (c *routeGuideClient) GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error) {
    out := new(Feature)
    err := c.cc.Invoke(ctx, "/routeguide.RouteGuide/GetFeature", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

RouteGuideClientThe complete implementation of the interface can be implemented in thepb.goFound in the file.

Method to call the service

Now let’s see how to call the methods of the service. Note that in grpc go, PRC runs in blocking / synchronization mode, that is, RPC calls will wait for a response from the server, and the server will return a response or an error.

Ordinary RPC

Call normal RPC methodGetFeatureIt’s like calling local methods directly.

feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906})
if err != nil {
        ...
}

As you can see, we call this method on the stub we obtained earlier. In our method parameters, we create and populate a protocol buffer object (in this case, the point object). We’ll also pass on onecontext.ContextObject that allows us to change RPC behavior if necessary, such as timeout / cancel an RPC in flight. If the call does not return an error, we can read the response information of the server from the first return value.

Service end streaming RPC

Here we will call the server-side streaming methodListFeatures, the flow returned by the method contains geographic information. If you’ve read the client creation section above, there’s something familiar here — streaming RPC is implemented in a similar way on both sides.

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)
}

Like a simple RPC call, the call passes a method context and a request. But what we got back was aRouteGuide_ListFeaturesClientInstance instead of a response object. Client can useRouteGuide_ListFeaturesClientStream reads the server’s response.

We useRouteGuide_ListFeaturesClientOfRecv()Method constantly reads the server’s response into a protocol buffer response object (in this case, theFeatureObject) until there are no more messages: the client needs to check theRecv()Error returnederr。 If sonil, the stream is still good and can continue to read; if it isio.EOF, the message flow has ended; otherwise, it is a certain RPC error, which will passerrPassed to the caller.

Client streaming RPC

Client stream methodRecordRouteSimilar to the server-side method, the difference is that we pass only one context to the method and get oneRouteGuide_RecordRouteClientStream that can be used to write and read messages.

//Randomly create some 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()) // call the client streaming RPC method defined in the service
if err != nil {
    log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {
    If err: = stream. Send (point); err! = nil {// write multiple request messages to the stream
        if err == io.EOF {
            break
        }
        log.Fatalf("%v.Send(%v) = %v", stream, point, err)
    }
}
Reply, err: = stream. Closeandrecv() // retrieve the server's response from the stream
if err != nil {
    log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v", reply)

RouteGuide_RecordRouteClientThere is oneSend()。 We can use it to send requests to the server. Once we useAfter the send() write stream is complete, we need to call on the streamCloseAndRecv()Method to let grpc know that we have finished writing the request and expect a response. We fromCloseAndRecv()Method returnederrRPC status is available in. If the status isnil, The first return value of closeandrecv() ‘is a valid server response.

Bidirectional flow RPC

Finally, let’s look at two-way streaming RPCRouteChat()。 AndRecordRouteSimilarly, we pass only one context object to the method and then get a stream that can be used to write and read messages. This time, however, we return a value through the flow of the method while the server still writes the message to the message flow.

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

Except for theCloseSend()Method, the read-write syntax here is very similar to our client stream method. Although both sides always get each other’s messages according to the other’s write order, both the client and the server can read and write in any order – the streams on both sides run completely independently.

Startup application

To compile and run the server, assume that you are at$ GOPATH/src/google.golang.org/grpc/examples/route_guideFolder, just:

$ go run server/server.go

Also, run the client:

$ go run client/client.go

Recommended Today

PHP Basics – String Array Operations

In our daily work, we often need to deal with some strings or arrays. Today, we have time to sort them out String operation <?php //String truncation $str = ‘Hello World!’ Substr ($STR, 0,5); // return ‘hello’ //Chinese string truncation $STR = ‘Hello, Shenzhen’; $result = mb_ Substr ($STR, 0,2); // Hello //First occurrence of […]