Golang gRPC学习(03): grpc官方示例程序route_guide简析
代码主要来源于grpc的官方examples代码:
route_guide
https://github.com/grpc/grpc-go/tree/master/examples/route_guide
服务定义 RouteGuide
service RouteGuide {
// A simple RPC.
//
// Obtains the feature at a given position.
//
// A feature with an empty name is returned if there's no feature at the given
// position.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.
//
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// A client-to-server streaming RPC.
//
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// A Bidirectional streaming RPC.
//
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
从定义里看:
rpc GetFeature(Point) returns (Feature) : 定义最简单的RPC服务
rpc ListFeatures(Rectangle) returns (stream Feature): 带有 stream 的RPC服务,返回是stream
rpc RecordRoute(stream Point) returns (RouteSummary):带有 stream 的RPC服务,客户端是stream
rpc RouteChat(stream RouteNote) returns (stream RouteNote):2端都是stream的RPC服务
rpc服务其他参数定义:
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;
}
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
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;
}
route_guide.pb.go
route_guide.pb.go,这个文件是干嘛的?
这个是grpc自动生成的文件,里面有序列化,反序列化,执行是函数。
先看看里面的接口,其中里面有2个主要接口:
- 1.RouteGuideClient interface
- 2.RouteGuideServer interface
1.RouteGuideClient interface定义
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)
}
仔细看看,这里面定义的一些方法,都是route_guide.proto文件里的service RouteGuide里的rpc方法,而且是一一对应的。
这个就是client需要操作的方法,是grpc自动生成的。客户端请求这些方法来调用服务。
从这里可以看出,grpc把proto中定义的服务映射为了一个interface,里面包含service中定义的rpc方法。
2.RouteGuideServer interface
type RouteGuideServer interface {
GetFeature(context.Context, *Point) (*Feature, error)
ListFeatures(*Rectangle, RouteGuide_ListFeaturesServer) error
RecordRoute(RouteGuide_RecordRouteServer) error
RouteChat(RouteGuide_RouteChatServer) error
}
这个也是与route_guide.proto文件里的service RouteGuide里的rpc方法是一一对应的。
同理,这里跟上面的RouteGuideClient一样,把service映射成了interface。
客户端请求client.go
看看3个用stream发送数据的函数,客户端用stream,服务端用stream,2端都用stream,
ListFeatures(Rectangle) returns (stream Feature) 方法
我们先看看 rpc ListFeatures(Rectangle) returns (stream Feature) {} 这个rpc方法,它返回的是一个 stream,在 client.go 文件里它是用 printFeatures() 这个函数来表示的,
// printFeatures lists all the features within the given bounding Rectangle.
func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {
log.Printf("Looking for features within %v", rect)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
//这里调用 ListFeatures(), rpc定义的方法,返回一个stream
stream, err := client.ListFeatures(ctx, rect)
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
for {//for循环不断的接收数据
feature, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
log.Println(feature)
}
}
RecordRoute(stream Point) returns (RouteSummary)
这个 rpc RecordRoute(stream Point) returns (RouteSummary) 方法,通过stream发送消息,在 client.go 文件里是 runRecordRoute() 方法
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))
}
log.Printf("Traversing %d points.", len(points))
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 调用RecordRoute() 方法
stream, err := client.RecordRoute(ctx)
if err != nil {
log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {
// stream.Send() stream方式发送数据
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)
}
RouteChat(stream RouteNote) returns (stream RouteNote)
这个是 rpc RouteChat(stream RouteNote) returns (stream RouteNote),2端都是操作stream,在 client.go 里面 runRouteChat()
func runRouteChat(client pb.RouteGuideClient) {
notes := []*pb.RouteNote{
{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "First message"},
{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Second message"},
{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Third message"},
{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "Fourth message"},
{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Fifth message"},
{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Sixth message"},
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := client.RouteChat(ctx)
if err != nil {
log.Fatalf("%v.RouteChat(_) = _, %v", client, err)
}
waitc := make(chan struct{})
go func() { //开一个协程来执行接收的动作
for {
in, err := stream.Recv() //stream方式接收
if err == io.EOF {
// read done.
close(waitc) //读取完成close掉chan,给外面的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)
}
}()
// 在main协程里面 strem 发送数据
for _, note := range notes {
if err := stream.Send(note); err != nil {
log.Fatalf("Failed to send a note: %v", err)
}
}
stream.CloseSend() // 关闭stream
<-waitc //stream 接收完成通知退出协程,main协程也结束运行
}
服务端server.go
定义了一个struct,routeGuideServer struct,然后是操作这个struct,
type routeGuideServer struct {
pb.UnimplementedRouteGuideServer
savedFeatures []*pb.Feature // read-only after initialized
mu sync.Mutex // protects routeNotes
routeNotes map[string][]*pb.RouteNote
}
rpc GetFeature()
这个rpc方法,客户端和服务端都不是stream方式发送,获取,
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
}
里面有一个比较的函数 proto.Equal(a, b Message) bool,在 protobuf/proto/equal.go 里,比较2值是否相等。
rpc ListFeatures()
rpc ListFeatures(Rectangle) returns (stream Feature)
这个rpc方法返回是一个stream,也就是服务端发送是stream方式发送,
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
for _, feature := range s.savedFeatures {
if inRange(feature.Location, rect) {
//stream 方式发送
if err := stream.Send(feature); err != nil {
return err
}
}
}
return nil
}
rpc RecordRoute
rpc RecordRoute(stream Point) returns (RouteSummary)
客户端发送(请求服务端)的数据是一个stream,那服务端server接收肯定也要用stream,
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() //接收stream
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
}
}
RecordRoute 函数的参数 pb.RouteGuide_RecordRouteServer 是什么?
它在 route_guide.pg.go 定义的是一个 interface,里面有SendAndClose(), Recv() 方法,
type RouteGuide_RecordRouteServer interface {
SendAndClose(*RouteSummary) error
Recv() (*Point, error)
grpc.ServerStream
}
rpc RouteChat
rpc RouteChat(stream RouteNote) returns (stream RouteNote)
这个rpc方法,客户端和服务端都是stream发送,接收,
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
for {
in, err := stream.Recv() // stream接收
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 { //stream发送
return err
}
}
}
}
newServer
// 运行服务端函数,返回一个struct,这个struct又实现了interface,
//所以main函数里可以直接调用 pb.RegisterRouteGuideServer(grpcServer, newServer())
func newServer() *routeGuideServer {
s := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)}
s.loadFeatures(*jsonDBFile)
return s
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
if *tls {
if *certFile == "" {
*certFile = testdata.Path("server1.pem")
}
if *keyFile == "" {
*keyFile = testdata.Path("server1.key")
}
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
opts = []grpc.ServerOption{grpc.Creds(creds)}
}
grpcServer := grpc.NewServer(opts...)
pb.RegisterRouteGuideServer(grpcServer, newServer())
grpcServer.Serve(lis)
}
服务端一些辅助函数
server.go 文件里面还有一些辅助函数:
1.loadFeatures
// 加载json形式的 features 数据,经纬度,名称
func (s *routeGuideServer) loadFeatures(filePath string)
下面这种格式的数据,跟 route_guide.proto 里 message Feature 数据定义一致
{
"location": {
"latitude": 407838351,
"longitude": -746143763
},
"name": "Patriots Path, Mendham, NJ 07945, USA"
}
2.toRadians
计算弧度
3.calcDistance
计算距离
4.inRange
在范围内
5.serialize
序列化
Golang gRPC学习(03): grpc官方示例程序route_guide简析的更多相关文章
- DotNetBar for Windows Forms 12.7.0.10_冰河之刃重打包版原创发布-带官方示例程序版
关于 DotNetBar for Windows Forms 12.7.0.10_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版------------- ...
- DotNetBar for Windows Forms 12.5.0.2_冰河之刃重打包版原创发布-带官方示例程序版
关于 DotNetBar for Windows Forms 12.5.0.2_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...
- DotNetBar for Windows Forms 12.2.0.7_冰河之刃重打包版原创发布-带官方示例程序版
关于 DotNetBar for Windows Forms 12.2.0.7_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...
- Ionic 2 官方示例程序 Super Starter
原文发表于我的技术博客 本文分享了 Ionic 2 官方示例程序 Super Starter 的简要介绍与安装运行的方法,最好的学习示例代码,项目共包含了 14 个通用的页面设计,如:引导页.主页面详 ...
- Angular 快速学习笔记(1) -- 官方示例要点
创建组件 ng generate component heroes {{ hero.name }} {{}}语法绑定数据 管道pipe 格式化数据 <h2>{{ hero.name | u ...
- opencv —— 官方 示例程序
OpenCV 官方提供的示例程序,具体位于...\opencv\sources\samples\cpp 目录下. ...\opencv\sources\samples\cpp\tutorial_cod ...
- SparkStreaming官方示例程序运行方式
一.前置条件 安装NetCat(有“瑞士军刀”之称,简称nc),输入如下命令: yum install -y nc 二.方式一:直接运行官方Example 2.1 打开一个shell,输入命令:nc ...
- 微信JS-SDK官方示例程序
示例地址:http://203.195.235.76/jssdk/ /* * 注意: * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能 ...
- python 学习笔记 9 -- Python强大的自省简析
1. 什么是自省? 自省就是自我评价.自我反省.自我批评.自我调控和自我教育,是孔子提出的一种自我道德修养的方法.他说:“见贤思齐焉,见不贤而内自省也.”(<论语·里仁>)当然,我们今天不 ...
随机推荐
- Qt-数据库操作SQLite
1 简介 参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=88 说明:本文对在Qt中操作SQLite做简要说明. SQLite:SQLite 是 ...
- 设计模式:abstract factory模式
含义:抽象工厂将“抽象零件”组装成“抽象产品” 理解:相比于工厂方法模式,可以根据不同的接口创建不同的产品,说白了就是将一个接口变成两个接口,各自返回不同的抽象产品 例子: class Car //抽 ...
- 加班两个星期做的一个小系统~(winform)
不管怎么样~加班两个星期,单独一人,努力将公司需要用的系统给做出来了,也感谢提供技术帮助的可爱人儿~ 首先,系统有个检测版本的功能,若版本不是最新的,则会自动更新(公司要求,必须强制更新)~ 更新界面 ...
- SmartMS如何使用二次验证码/虚拟MFA/两步验证/谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 SmartMS如何使用二次验证码/虚拟MFA/两步验证/谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无 ...
- 印象笔记如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 印象笔记如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载a ...
- .net core 自带分布式事务的微服务开源框架JMS
事务的统一性是微服务的一个重点问题,简洁有效的控制事务,更是程序员所需要的.JMS的诞生,就是为了更简单.更有效的控制事务. 先看一段调用微服务的代码: using (var ms = new JMS ...
- 面试题三十:包含min函数的栈
定义一个栈的数据结构,请实现一个每次都能找到栈中的最小元素,要求时间复杂度O(1).意思就是说每次进栈出栈后,min函数总能在时间1的前提下找到.方法一:由于每次循序遍历栈的话时间复杂度为n,所以要想 ...
- 【新生学习】深度学习与 PyTorch 实战课程大纲
各位20级新同学好,我安排的课程没有教材,只有一些视频.论文和代码.大家可以看看大纲,感兴趣的同学参加即可.因为是第一次开课,大纲和进度会随时调整,同学们可以随时关注.初步计划每周两章,一个半月完成课 ...
- 线程_Process基础语法
""" Process([group[,target[,name[,args[,kwargs]]]]]) group:大多数情况下用不到 target:表示这个进程实例所 ...
- PHP array_keys() 函数
实例 返回包含数组中所有键名的一个新数组: <?php$a=array("Volvo"=>"XC90","BMW"=>&q ...