Go 语言中的 gRPC 基础入门(下)

简介: Go 语言中的 gRPC 基础入门(下)

05

创建客户端


在本部分中,我们将研究为 RouteGuide 服务创建 Go 客户端。

您可以在 grpc-go/examples/route_guide/client/client.go 中看到我们完整的示例客户端代码。


创建客户端存根


要调用服务方法,我们首先需要创建一个 gRPC 通道来与服务器通信。我们通过将服务器地址和端口号传递给 grpc.Dial() 来创建它,如下所示:


var opts []grpc.DialOption
...
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
  ...
}
defer conn.Close()


当服务需要它们时,可以使用 DialOptions 在 grpc.Dial 中设置身份验证凭据(例如TLS,GCE凭据或JWT凭据)。RouteGuide 服务不需要任何凭据。


设置 gRPC 通道后,我们需要一个客户端存根来执行 RPC。我们使用从示例 .proto 文件生成的 pb 包提供的 NewRouteGuideClient 方法获取它。


client := pb.NewRouteGuideClient(conn)


调用服务方法


现在,让我们看看我们如何调用我们的服务方法。请注意,在 gRPC-Go 中,RPC 在阻塞/同步模式下运行,这意味着 RPC 调用等待服务器响应,并且将返回响应或错误。


简单的 RPC


调用简单的 RPC GetFeature 几乎与调用本地方法一样简单。


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


如您所见,我们在先前获得的存根上调用该方法。在我们的方法参数中,我们创建并填充一个请求 protocol buffer 对象(在本例中为 Point)。我们还会传递一个 context.Context 对象,该对象可让我们在必要时更改 RPC 的行为,例如 time-out/cancel 运行中的 RPC。如果调用没有返回错误,那么我们可以从服务器的第一个返回值中读取响应信息。


log.Println(feature)


服务器端流式 RPC


我们在这里调用服务器端流方法 ListFeatures,该方法返回地理要素流。如果您已经阅读了创建服务器的内容,那么其中的一些内容可能看起来非常熟悉-流式 RPC 在两侧都以类似的方式实现。


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


就像在简单的 RPC 中一样,我们为该方法传递一个上下文和一个请求。但是,我们没有取回响应对象,而是取回

RouteGuide_ListFeaturesClient 的实例。

客户端可以使用 RouteGuide_ListFeaturesClient 流读取服务器的响应。


我们使用

RouteGuide_ListFeaturesClient 的 Recv() 方法重复读取服务器对响应 protocol buffer 对象(在本例中为 Feature)的响应,直到没有更多消息为止:客户端需要检查每次返回后从 Recv() 返回的错误 err。如果为 nil,则流仍然良好,并且可以继续读取;如果是 io.EOF,则消息流已结束;否则,必须存在 RPC 错误,该错误会通过 err 传递。



客户端流式 RPC


客户端流方法 RecordRoute 与服务器端方法相似,不同之处在于,我们仅向该方法传递上下文,并获取回

RouteGuide_RecordRouteClientClient 流,我们可以使用该流来写入和读取消息。


// 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))
stream, err := client.RecordRoute(context.Background())
if err != nil {
  log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {
  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)


RouteGuide_RecordRouteClient 具有一个 Send() 方法,可用于将请求发送到服务器。使用 Send() 完成将客户的请求写入流中后,我们需要在流上调用 CloseAndRecv() ,以使 gRPC 知道我们已完成写入并期望收到响应。我们从 CloseAndRecv() 返回的错误中获取 RPC 状态。如果状态为 nil,则 CloseAndRecv() 的第一个返回值将是有效的服务器响应。


双向流式 RPC


最后,让我们看一下双向流式 RPC RouteChat() 。

与 RecordRoute 一样,我们只向方法传递一个上下文对象,然后获取可用于写入和读取消息的流。但是,这一次我们在服务器仍将消息写入消息流的同时,我们还通过方法的流返回值。


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


除了在完成调用后使用流的 CloseSend() 方法外,此处的读写语法与我们的客户端流方法非常相似。尽管双方总是会按照对方的写入顺序来获取对方的消息,但是客户端和服务器都可以以任意顺序进行读取和写入-流完全独立地运行。


06

运行程序


从 examples/route_guide 目录执行以下命令:

  1. 运行服务器:
$ go run server/server.go
  1. 从另一个终端,运行客户端:
$ go run client/client.go


输出内容:


Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
Looking for features within lo:<latitude:400000000 longitude:-750000000 > hi:<latitude:420000000 longitude:-730000000 >
name:"Patriots Path, Mendham, NJ 07945, USA" location:<latitude:407838351 longitude:-746143763 >
...
name:"3 Hasta Way, Newton, NJ 07860, USA" location:<latitude:410248224 longitude:-747127767 >
Traversing 56 points.
Route summary: point_count:56 distance:497013163
Got message First message at point(0, 1)
Got message Second message at point(0, 2)
Got message Third message at point(0, 3)
Got message First message at point(0, 1)
Got message Fourth message at point(0, 1)
Got message Second message at point(0, 2)
Got message Fifth message at point(0, 2)
Got message Third message at point(0, 3)
Got message Sixth message at point(0, 3)



注意:

我们已从本页显示的客户端和服务器跟踪输出中省略了时间戳。


07

总结


本文开篇先介绍了为什么要使用 gRPC,接着简述了使用 gRPC 需要做的准备工作,然后通过 gRPC 官方 Go 示例代码介绍了如何在 .proto 文件中定义服务,如何使用 protoc 编译器生成客户端和服务器代码,如何根据 protoc 编译器生成的客户端和服务器代码创建服务器和客户端的 4 种形式。





目录
相关文章
|
3天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
16 4
|
3天前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
27 1
|
6天前
|
Go
go语言中的continue 语句
go语言中的continue 语句
16 3
|
7天前
|
安全 Go 调度
探索Go语言的并发模型:goroutine与channel
在这个快节奏的技术世界中,Go语言以其简洁的并发模型脱颖而出。本文将带你深入了解Go语言的goroutine和channel,这两个核心特性如何协同工作,以实现高效、简洁的并发编程。
|
8天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
17 4
|
8天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
40 1
|
1天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
11 0
|
21天前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
33 3
|
22天前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
29 3
|
3月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
135 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
下一篇
无影云桌面