Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 1 | #gRPC Basics: C++ |
| 2 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 3 | This tutorial provides a basic C++ programmer's introduction to working with |
| 4 | gRPC. By walking through this example you'll learn how to: |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 5 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 6 | - Define a service in a `.proto` file. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 7 | - Generate server and client code using the protocol buffer compiler. |
| 8 | - Use the C++ gRPC API to write a simple client and server for your service. |
| 9 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 10 | It assumes that you are familiar with |
| 11 | [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). |
| 12 | Note that the example in this tutorial uses the proto3 version of the protocol |
| 13 | buffers language, which is currently in alpha release: you can find out more in |
| 14 | the [proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) |
| 15 | and see the [release notes](https://github.com/google/protobuf/releases) for the |
| 16 | new version in the protocol buffers Github repository. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 17 | |
| 18 | ## Why use gRPC? |
| 19 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 20 | Our example is a simple route mapping application that lets clients get |
| 21 | information about features on their route, create a summary of their route, and |
| 22 | exchange route information such as traffic updates with the server and other |
| 23 | clients. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 24 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 25 | With gRPC we can define our service once in a `.proto` file and implement clients |
| 26 | and servers in any of gRPC's supported languages, which in turn can be run in |
| 27 | environments ranging from servers inside Google to your own tablet - all the |
| 28 | complexity of communication between different languages and environments is |
| 29 | handled for you by gRPC. We also get all the advantages of working with protocol |
| 30 | buffers, including efficient serialization, a simple IDL, and easy interface |
| 31 | updating. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 32 | |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 33 | ## Example code and setup |
| 34 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 35 | The example code for our tutorial is in [examples/cpp/route_guide](route_guide). |
| 36 | You also should have the relevant tools installed to generate the server and |
| 37 | client interface code - if you don't already, follow the setup instructions in |
| 38 | [INSTALL.md](../../INSTALL.md). |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 39 | |
| 40 | ## Defining the service |
| 41 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 42 | Our first step is to define the gRPC *service* and the method *request* and |
| 43 | *response* types using |
| 44 | [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). |
| 45 | You can see the complete `.proto` file in |
| 46 | [`examples/protos/route_guide.proto`](../protos/route_guide.proto). |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 47 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 48 | To define a service, you specify a named `service` in your `.proto` file: |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 49 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 50 | ```protobuf |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 51 | service RouteGuide { |
| 52 | ... |
| 53 | } |
| 54 | ``` |
| 55 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 56 | Then you define `rpc` methods inside your service definition, specifying their |
| 57 | request and response types. gRPC lets you define four kinds of service method, |
| 58 | all of which are used in the `RouteGuide` service: |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 59 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 60 | - A *simple RPC* where the client sends a request to the server using the stub |
| 61 | and waits for a response to come back, just like a normal function call. |
| 62 | |
| 63 | ```protobuf |
Yang Gao | de0c653 | 2015-02-24 15:52:22 -0800 | [diff] [blame] | 64 | // Obtains the feature at a given position. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 65 | rpc GetFeature(Point) returns (Feature) {} |
| 66 | ``` |
| 67 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 68 | - A *server-side streaming RPC* where the client sends a request to the server |
| 69 | and gets a stream to read a sequence of messages back. The client reads from |
| 70 | the returned stream until there are no more messages. As you can see in our |
| 71 | example, you specify a server-side streaming method by placing the `stream` |
| 72 | keyword before the *response* type. |
| 73 | |
| 74 | ```protobuf |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 75 | // Obtains the Features available within the given Rectangle. Results are |
| 76 | // streamed rather than returned at once (e.g. in a response message with a |
| 77 | // repeated field), as the rectangle may cover a large area and contain a |
| 78 | // huge number of features. |
| 79 | rpc ListFeatures(Rectangle) returns (stream Feature) {} |
| 80 | ``` |
| 81 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 82 | - A *client-side streaming RPC* where the client writes a sequence of messages |
| 83 | and sends them to the server, again using a provided stream. Once the client |
| 84 | has finished writing the messages, it waits for the server to read them all |
| 85 | and return its response. You specify a client-side streaming method by placing |
| 86 | the `stream` keyword before the *request* type. |
| 87 | |
| 88 | ```protobuf |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 89 | // Accepts a stream of Points on a route being traversed, returning a |
| 90 | // RouteSummary when traversal is completed. |
| 91 | rpc RecordRoute(stream Point) returns (RouteSummary) {} |
| 92 | ``` |
| 93 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 94 | - A *bidirectional streaming RPC* where both sides send a sequence of messages |
| 95 | using a read-write stream. The two streams operate independently, so clients |
| 96 | and servers can read and write in whatever order they like: for example, the |
| 97 | server could wait to receive all the client messages before writing its |
| 98 | responses, or it could alternately read a message then write a message, or |
| 99 | some other combination of reads and writes. The order of messages in each |
| 100 | stream is preserved. You specify this type of method by placing the `stream` |
| 101 | keyword before both the request and the response. |
| 102 | |
| 103 | ```protobuf |
Lisa Carey | 450d112 | 2015-02-23 16:05:25 +0000 | [diff] [blame] | 104 | // Accepts a stream of RouteNotes sent while a route is being traversed, |
| 105 | // while receiving other RouteNotes (e.g. from other users). |
| 106 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 107 | ``` |
| 108 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 109 | Our `.proto` file also contains protocol buffer message type definitions for all |
| 110 | the request and response types used in our service methods - for example, here's |
| 111 | the `Point` message type: |
| 112 | |
| 113 | ```protobuf |
Lisa Carey | 450d112 | 2015-02-23 16:05:25 +0000 | [diff] [blame] | 114 | // Points are represented as latitude-longitude pairs in the E7 representation |
| 115 | // (degrees multiplied by 10**7 and rounded to the nearest integer). |
| 116 | // Latitudes should be in the range +/- 90 degrees and longitude should be in |
| 117 | // the range +/- 180 degrees (inclusive). |
| 118 | message Point { |
| 119 | int32 latitude = 1; |
| 120 | int32 longitude = 2; |
| 121 | } |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 122 | ``` |
| 123 | |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 124 | ## Generating client and server code |
| 125 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 126 | Next we need to generate the gRPC client and server interfaces from our `.proto` |
| 127 | service definition. We do this using the protocol buffer compiler `protoc` with |
| 128 | a special gRPC C++ plugin. |
Lisa Carey | 7a21966 | 2015-02-23 16:42:21 +0000 | [diff] [blame] | 129 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 130 | For simplicity, we've provided a [makefile](route_guide/Makefile) that runs |
| 131 | `protoc` for you with the appropriate plugin, input, and output (if you want to |
| 132 | run this yourself, make sure you've installed protoc and followed the gRPC code |
| 133 | [installation instructions](../../INSTALL.md) first): |
Lisa Carey | 7a21966 | 2015-02-23 16:42:21 +0000 | [diff] [blame] | 134 | |
| 135 | ```shell |
Nicolas "Pixel" Noble | b6413de | 2015-04-10 00:24:09 +0200 | [diff] [blame] | 136 | $ make route_guide.grpc.pb.cc route_guide.pb.cc |
Lisa Carey | 7a21966 | 2015-02-23 16:42:21 +0000 | [diff] [blame] | 137 | ``` |
| 138 | |
| 139 | which actually runs: |
| 140 | |
Yang Gao | cdbb60c | 2015-02-24 15:01:36 -0800 | [diff] [blame] | 141 | ```shell |
Nicolas "Pixel" Noble | b6413de | 2015-04-10 00:24:09 +0200 | [diff] [blame] | 142 | $ protoc -I ../../protos --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` ../../protos/route_guide.proto |
| 143 | $ protoc -I ../../protos --cpp_out=. ../../protos/route_guide.proto |
Yang Gao | cdbb60c | 2015-02-24 15:01:36 -0800 | [diff] [blame] | 144 | ``` |
Lisa Carey | 7a21966 | 2015-02-23 16:42:21 +0000 | [diff] [blame] | 145 | |
LisaFC | 25ffbd8 | 2015-02-25 14:12:39 +0000 | [diff] [blame] | 146 | Running this command generates the following files in your current directory: |
Nicolas "Pixel" Noble | b6413de | 2015-04-10 00:24:09 +0200 | [diff] [blame] | 147 | - `route_guide.pb.h`, the header which declares your generated message classes |
| 148 | - `route_guide.pb.cc`, which contains the implementation of your message classes |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 149 | - `route_guide.grpc.pb.h`, the header which declares your generated service |
| 150 | classes |
| 151 | - `route_guide.grpc.pb.cc`, which contains the implementation of your service |
| 152 | classes |
Lisa Carey | 7a21966 | 2015-02-23 16:42:21 +0000 | [diff] [blame] | 153 | |
Lisa Carey | 453eca3 | 2015-02-23 16:58:19 +0000 | [diff] [blame] | 154 | These contain: |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 155 | - All the protocol buffer code to populate, serialize, and retrieve our request |
| 156 | and response message types |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 157 | - A class called `RouteGuide` that contains |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 158 | - a remote interface type (or *stub*) for clients to call with the methods |
| 159 | defined in the `RouteGuide` service. |
| 160 | - two abstract interfaces for servers to implement, also with the methods |
| 161 | defined in the `RouteGuide` service. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 162 | |
| 163 | |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 164 | <a name="server"></a> |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 165 | ## Creating the server |
| 166 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 167 | First let's look at how we create a `RouteGuide` server. If you're only |
| 168 | interested in creating gRPC clients, you can skip this section and go straight |
| 169 | to [Creating the client](#client) (though you might find it interesting |
| 170 | anyway!). |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 171 | |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 172 | There are two parts to making our `RouteGuide` service do its job: |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 173 | - Implementing the service interface generated from our service definition: |
| 174 | doing the actual "work" of our service. |
| 175 | - Running a gRPC server to listen for requests from clients and return the |
| 176 | service responses. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 177 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 178 | You can find our example `RouteGuide` server in |
| 179 | [route_guide/route_guide_server.cc](route_guide/route_guide_server.cc). Let's |
| 180 | take a closer look at how it works. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 181 | |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 182 | ### Implementing RouteGuide |
| 183 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 184 | As you can see, our server has a `RouteGuideImpl` class that implements the |
| 185 | generated `RouteGuide::Service` interface: |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 186 | |
| 187 | ```cpp |
| 188 | class RouteGuideImpl final : public RouteGuide::Service { |
| 189 | ... |
| 190 | } |
| 191 | ``` |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 192 | In this case we're implementing the *synchronous* version of `RouteGuide`, which |
| 193 | provides our default gRPC server behaviour. It's also possible to implement an |
| 194 | asynchronous interface, `RouteGuide::AsyncService`, which allows you to further |
| 195 | customize your server's threading behaviour, though we won't look at this in |
| 196 | this tutorial. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 197 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 198 | `RouteGuideImpl` implements all our service methods. Let's look at the simplest |
| 199 | type first, `GetFeature`, which just gets a `Point` from the client and returns |
| 200 | the corresponding feature information from its database in a `Feature`. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 201 | |
| 202 | ```cpp |
| 203 | Status GetFeature(ServerContext* context, const Point* point, |
| 204 | Feature* feature) override { |
| 205 | feature->set_name(GetFeatureName(*point, feature_list_)); |
| 206 | feature->mutable_location()->CopyFrom(*point); |
| 207 | return Status::OK; |
| 208 | } |
| 209 | ``` |
| 210 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 211 | The method is passed a context object for the RPC, the client's `Point` protocol |
| 212 | buffer request, and a `Feature` protocol buffer to fill in with the response |
| 213 | information. In the method we populate the `Feature` with the appropriate |
| 214 | information, and then `return` with an `OK` status to tell gRPC that we've |
| 215 | finished dealing with the RPC and that the `Feature` can be returned to the |
| 216 | client. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 217 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 218 | Now let's look at something a bit more complicated - a streaming RPC. |
| 219 | `ListFeatures` is a server-side streaming RPC, so we need to send back multiple |
| 220 | `Feature`s to our client. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 221 | |
| 222 | ```cpp |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 223 | Status ListFeatures(ServerContext* context, const Rectangle* rectangle, |
| 224 | ServerWriter<Feature>* writer) override { |
| 225 | auto lo = rectangle->lo(); |
| 226 | auto hi = rectangle->hi(); |
| 227 | long left = std::min(lo.longitude(), hi.longitude()); |
| 228 | long right = std::max(lo.longitude(), hi.longitude()); |
| 229 | long top = std::max(lo.latitude(), hi.latitude()); |
| 230 | long bottom = std::min(lo.latitude(), hi.latitude()); |
| 231 | for (const Feature& f : feature_list_) { |
| 232 | if (f.location().longitude() >= left && |
| 233 | f.location().longitude() <= right && |
| 234 | f.location().latitude() >= bottom && |
| 235 | f.location().latitude() <= top) { |
| 236 | writer->Write(f); |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 237 | } |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 238 | } |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 239 | return Status::OK; |
| 240 | } |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 241 | ``` |
| 242 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 243 | As you can see, instead of getting simple request and response objects in our |
| 244 | method parameters, this time we get a request object (the `Rectangle` in which |
| 245 | our client wants to find `Feature`s) and a special `ServerWriter` object. In the |
| 246 | method, we populate as many `Feature` objects as we need to return, writing them |
| 247 | to the `ServerWriter` using its `Write()` method. Finally, as in our simple RPC, |
| 248 | we `return Status::OK` to tell gRPC that we've finished writing responses. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 249 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 250 | If you look at the client-side streaming method `RecordRoute` you'll see it's |
| 251 | quite similar, except this time we get a `ServerReader` instead of a request |
| 252 | object and a single response. We use the `ServerReader`s `Read()` method to |
| 253 | repeatedly read in our client's requests to a request object (in this case a |
| 254 | `Point`) until there are no more messages: the server needs to check the return |
| 255 | value of `Read()` after each call. If `true`, the stream is still good and it |
| 256 | can continue reading; if `false` the message stream has ended. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 257 | |
| 258 | ```cpp |
| 259 | while (stream->Read(&point)) { |
| 260 | ...//process client input |
| 261 | } |
| 262 | ``` |
| 263 | Finally, let's look at our bidirectional streaming RPC `RouteChat()`. |
| 264 | |
| 265 | ```cpp |
| 266 | Status RouteChat(ServerContext* context, |
| 267 | ServerReaderWriter<RouteNote, RouteNote>* stream) override { |
| 268 | std::vector<RouteNote> received_notes; |
| 269 | RouteNote note; |
| 270 | while (stream->Read(¬e)) { |
| 271 | for (const RouteNote& n : received_notes) { |
| 272 | if (n.location().latitude() == note.location().latitude() && |
| 273 | n.location().longitude() == note.location().longitude()) { |
| 274 | stream->Write(n); |
| 275 | } |
| 276 | } |
| 277 | received_notes.push_back(note); |
| 278 | } |
| 279 | |
| 280 | return Status::OK; |
| 281 | } |
| 282 | ``` |
| 283 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 284 | This time we get a `ServerReaderWriter` that can be used to read *and* write |
| 285 | messages. The syntax for reading and writing here is exactly the same as for our |
| 286 | client-streaming and server-streaming methods. Although each side will always |
| 287 | get the other's messages in the order they were written, both the client and |
| 288 | server can read and write in any order — the streams operate completely |
| 289 | independently. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 290 | |
| 291 | ### Starting the server |
| 292 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 293 | Once we've implemented all our methods, we also need to start up a gRPC server |
| 294 | so that clients can actually use our service. The following snippet shows how we |
| 295 | do this for our `RouteGuide` service: |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 296 | |
| 297 | ```cpp |
| 298 | void RunServer(const std::string& db_path) { |
| 299 | std::string server_address("0.0.0.0:50051"); |
| 300 | RouteGuideImpl service(db_path); |
| 301 | |
| 302 | ServerBuilder builder; |
Nicolas "Pixel" Noble | 2afb270 | 2015-03-23 23:44:31 +0100 | [diff] [blame] | 303 | builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 304 | builder.RegisterService(&service); |
| 305 | std::unique_ptr<Server> server(builder.BuildAndStart()); |
| 306 | std::cout << "Server listening on " << server_address << std::endl; |
Yang Gao | 44e9822 | 2015-02-24 16:06:02 -0800 | [diff] [blame] | 307 | server->Wait(); |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 308 | } |
| 309 | ``` |
| 310 | As you can see, we build and start our server using a `ServerBuilder`. To do this, we: |
| 311 | |
| 312 | 1. Create an instance of our service implementation class `RouteGuideImpl`. |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 313 | 1. Create an instance of the factory `ServerBuilder` class. |
| 314 | 1. Specify the address and port we want to use to listen for client requests |
| 315 | using the builder's `AddListeningPort()` method. |
| 316 | 1. Register our service implementation with the builder. |
| 317 | 1. Call `BuildAndStart()` on the builder to create and start an RPC server for |
| 318 | our service. |
| 319 | 1. Call `Wait()` on the server to do a blocking wait until process is killed or |
| 320 | `Shutdown()` is called. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 321 | |
| 322 | <a name="client"></a> |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 323 | ## Creating the client |
| 324 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 325 | In this section, we'll look at creating a C++ client for our `RouteGuide` |
| 326 | service. You can see our complete example client code in |
| 327 | [route_guide/route_guide_client.cc](route_guide/route_guide_client.cc). |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 328 | |
| 329 | ### Creating a stub |
| 330 | |
| 331 | To call service methods, we first need to create a *stub*. |
| 332 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 333 | First we need to create a gRPC *channel* for our stub, specifying the server |
| 334 | address and port we want to connect to without SSL: |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 335 | |
| 336 | ```cpp |
Julien Boeuf | 8c48a2a | 2015-10-17 22:23:02 -0700 | [diff] [blame] | 337 | grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 338 | ``` |
| 339 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 340 | Now we can use the channel to create our stub using the `NewStub` method |
| 341 | provided in the `RouteGuide` class we generated from our `.proto`. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 342 | |
| 343 | ```cpp |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 344 | public: |
| 345 | RouteGuideClient(std::shared_ptr<Channel> channel, const std::string& db) |
| 346 | : stub_(RouteGuide::NewStub(channel)) { |
| 347 | ... |
| 348 | } |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 349 | ``` |
| 350 | |
| 351 | ### Calling service methods |
| 352 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 353 | Now let's look at how we call our service methods. Note that in this tutorial |
| 354 | we're calling the *blocking/synchronous* versions of each method: this means |
| 355 | that the RPC call waits for the server to respond, and will either return a |
| 356 | response or raise an exception. |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 357 | |
Lisa Carey | 88a49f6 | 2015-02-24 18:09:38 +0000 | [diff] [blame] | 358 | #### Simple RPC |
| 359 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 360 | Calling the simple RPC `GetFeature` is nearly as straightforward as calling a |
| 361 | local method. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 362 | |
| 363 | ```cpp |
| 364 | Point point; |
| 365 | Feature feature; |
| 366 | point = MakePoint(409146138, -746188906); |
| 367 | GetOneFeature(point, &feature); |
| 368 | |
| 369 | ... |
| 370 | |
| 371 | bool GetOneFeature(const Point& point, Feature* feature) { |
| 372 | ClientContext context; |
| 373 | Status status = stub_->GetFeature(&context, point, feature); |
| 374 | ... |
| 375 | } |
| 376 | ``` |
| 377 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 378 | As you can see, we create and populate a request protocol buffer object (in our |
| 379 | case `Point`), and create a response protocol buffer object for the server to |
| 380 | fill in. We also create a `ClientContext` object for our call - you can |
| 381 | optionally set RPC configuration values on this object, such as deadlines, |
| 382 | though for now we'll use the default settings. Note that you cannot reuse this |
| 383 | object between calls. Finally, we call the method on the stub, passing it the |
| 384 | context, request, and response. If the method returns `OK`, then we can read the |
| 385 | response information from the server from our response object. |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 386 | |
| 387 | ```cpp |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 388 | std::cout << "Found feature called " << feature->name() << " at " |
| 389 | << feature->location().latitude()/kCoordFactor_ << ", " |
| 390 | << feature->location().longitude()/kCoordFactor_ << std::endl; |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 391 | ``` |
| 392 | |
Lisa Carey | 88a49f6 | 2015-02-24 18:09:38 +0000 | [diff] [blame] | 393 | #### Streaming RPCs |
| 394 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 395 | Now let's look at our streaming methods. If you've already read [Creating the |
| 396 | server](#server) some of this may look very familiar - streaming RPCs are |
| 397 | implemented in a similar way on both sides. Here's where we call the server-side |
| 398 | streaming method `ListFeatures`, which returns a stream of geographical |
| 399 | `Feature`s: |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 400 | |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 401 | ```cpp |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 402 | std::unique_ptr<ClientReader<Feature> > reader( |
| 403 | stub_->ListFeatures(&context, rect)); |
| 404 | while (reader->Read(&feature)) { |
| 405 | std::cout << "Found feature called " |
| 406 | << feature.name() << " at " |
| 407 | << feature.location().latitude()/kCoordFactor_ << ", " |
| 408 | << feature.location().longitude()/kCoordFactor_ << std::endl; |
| 409 | } |
| 410 | Status status = reader->Finish(); |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 411 | ``` |
| 412 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 413 | Instead of passing the method a context, request, and response, we pass it a |
| 414 | context and request and get a `ClientReader` object back. The client can use the |
| 415 | `ClientReader` to read the server's responses. We use the `ClientReader`s |
| 416 | `Read()` method to repeatedly read in the server's responses to a response |
| 417 | protocol buffer object (in this case a `Feature`) until there are no more |
| 418 | messages: the client needs to check the return value of `Read()` after each |
| 419 | call. If `true`, the stream is still good and it can continue reading; if |
| 420 | `false` the message stream has ended. Finally, we call `Finish()` on the stream |
| 421 | to complete the call and get our RPC status. |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 422 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 423 | The client-side streaming method `RecordRoute` is similar, except there we pass |
| 424 | the method a context and response object and get back a `ClientWriter`. |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 425 | |
| 426 | ```cpp |
| 427 | std::unique_ptr<ClientWriter<Point> > writer( |
| 428 | stub_->RecordRoute(&context, &stats)); |
| 429 | for (int i = 0; i < kPoints; i++) { |
| 430 | const Feature& f = feature_list_[feature_distribution(generator)]; |
| 431 | std::cout << "Visiting point " |
| 432 | << f.location().latitude()/kCoordFactor_ << ", " |
| 433 | << f.location().longitude()/kCoordFactor_ << std::endl; |
| 434 | if (!writer->Write(f.location())) { |
| 435 | // Broken stream. |
| 436 | break; |
| 437 | } |
| 438 | std::this_thread::sleep_for(std::chrono::milliseconds( |
| 439 | delay_distribution(generator))); |
| 440 | } |
| 441 | writer->WritesDone(); |
| 442 | Status status = writer->Finish(); |
| 443 | if (status.IsOk()) { |
| 444 | std::cout << "Finished trip with " << stats.point_count() << " points\n" |
| 445 | << "Passed " << stats.feature_count() << " features\n" |
| 446 | << "Travelled " << stats.distance() << " meters\n" |
| 447 | << "It took " << stats.elapsed_time() << " seconds" |
| 448 | << std::endl; |
| 449 | } else { |
| 450 | std::cout << "RecordRoute rpc failed." << std::endl; |
| 451 | } |
| 452 | ``` |
| 453 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 454 | Once we've finished writing our client's requests to the stream using `Write()`, |
| 455 | we need to call `WritesDone()` on the stream to let gRPC know that we've |
| 456 | finished writing, then `Finish()` to complete the call and get our RPC status. |
| 457 | If the status is `OK`, our response object that we initially passed to |
| 458 | `RecordRoute()` will be populated with the server's response. |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 459 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 460 | Finally, let's look at our bidirectional streaming RPC `RouteChat()`. In this |
| 461 | case, we just pass a context to the method and get back a `ClientReaderWriter`, |
| 462 | which we can use to both write and read messages. |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 463 | |
| 464 | ```cpp |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 465 | std::shared_ptr<ClientReaderWriter<RouteNote, RouteNote> > stream( |
| 466 | stub_->RouteChat(&context)); |
Lisa Carey | fea9152 | 2015-02-24 18:07:45 +0000 | [diff] [blame] | 467 | ``` |
| 468 | |
David G. Quintas | 5804745 | 2016-08-11 15:40:56 -0700 | [diff] [blame^] | 469 | The syntax for reading and writing here is exactly the same as for our |
| 470 | client-streaming and server-streaming methods. Although each side will always |
| 471 | get the other's messages in the order they were written, both the client and |
| 472 | server can read and write in any order — the streams operate completely |
| 473 | independently. |
Lisa Carey | 54e0c6d | 2015-02-23 16:00:38 +0000 | [diff] [blame] | 474 | |
Lisa Carey | 14184fa | 2015-02-24 16:56:30 +0000 | [diff] [blame] | 475 | ## Try it out! |
| 476 | |
Yang Gao | 9a2ff4f | 2015-02-24 16:13:02 -0800 | [diff] [blame] | 477 | Build client and server: |
| 478 | ```shell |
| 479 | $ make |
| 480 | ``` |
| 481 | Run the server, which will listen on port 50051: |
| 482 | ```shell |
| 483 | $ ./route_guide_server |
| 484 | ``` |
| 485 | Run the client (in a different terminal): |
| 486 | ```shell |
| 487 | $ ./route_guide_client |
| 488 | ``` |