gRPC-Go 入门教程:从零开始构建路由指南服务
grpc-go 基于HTTP/2的gRPC的Go语言实现。 项目地址: https://gitcode.com/gh_mirrors/gr/grpc-go
本文将带你深入了解如何使用 gRPC-Go 框架构建一个完整的路由指南服务。我们将从基础概念讲起,逐步实现服务定义、代码生成、服务端和客户端开发等关键环节。
什么是 gRPC?
gRPC 是一个高性能、开源的通用 RPC 框架,由 Google 开发。它基于 HTTP/2 协议,使用 Protocol Buffers 作为接口定义语言(IDL)和消息交换格式。gRPC 的主要优势包括:
- 跨语言支持:服务端和客户端可以用不同语言实现
- 高效的二进制传输协议
- 支持四种通信模式
- 内置流式处理能力
- 强大的生态系统和工具链
项目概述
我们将构建一个路由指南服务,提供以下功能:
- 获取特定位置的地理特征信息
- 列出矩形区域内所有地理特征
- 记录路线并计算汇总信息
- 实时交换路线备注信息
服务定义
首先我们需要使用 Protocol Buffers 定义服务接口。创建一个 route_guide.proto
文件:
syntax = "proto3";
package routeguide;
service RouteGuide {
// 简单 RPC:获取单个特征
rpc GetFeature(Point) returns (Feature) {}
// 服务端流式 RPC:获取区域内的多个特征
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// 客户端流式 RPC:记录路线并返回汇总
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// 双向流式 RPC:实时交换路线备注
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
// 定义各种消息类型
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
message Feature {
string name = 1;
Point location = 2;
}
message Rectangle {
Point lo = 1;
Point hi = 2;
}
message RouteSummary {
int32 point_count = 1;
int32 feature_count = 2;
int32 distance = 3;
int32 elapsed_time = 4;
}
message RouteNote {
Point location = 1;
string message = 2;
}
代码生成
使用 protoc 编译器生成 Go 代码:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
route_guide.proto
这将生成两个文件:
route_guide.pb.go
:包含 Protocol Buffers 消息类型的编解码代码route_guide_grpc.pb.go
:包含服务端接口和客户端存根代码
实现服务端
服务端基础结构
首先定义服务端结构体并实现生成的接口:
type routeGuideServer struct {
savedFeatures []*pb.Feature
routeNotes map[string][]*pb.RouteNote
mu sync.Mutex
}
func newServer() *routeGuideServer {
return &routeGuideServer{
savedFeatures: []*pb.Feature{...},
routeNotes: make(map[string][]*pb.RouteNote),
}
}
实现简单 RPC
GetFeature
方法查找给定位置的地理特征:
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
}
}
// 未找到则返回空特征
return &pb.Feature{Location: point}, nil
}
实现服务端流式 RPC
ListFeatures
方法流式返回矩形区域内的所有特征:
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
}
实现客户端流式 RPC
RecordRoute
方法接收客户端发送的多个点,最后返回路线汇总:
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 {
return stream.SendAndClose(&pb.RouteSummary{
PointCount: pointCount,
FeatureCount: featureCount,
Distance: distance,
ElapsedTime: int32(time.Since(startTime).Seconds()),
})
}
// 处理每个点...
}
}
实现双向流式 RPC
RouteChat
方法实现双向实时通信:
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)
notes := s.routeNotes[key]
s.mu.Unlock()
for _, note := range notes {
if err := stream.Send(note); err != nil {
return err
}
}
}
}
启动服务
func main() {
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, newServer())
grpcServer.Serve(lis)
}
实现客户端
创建客户端存根
conn, err := grpc.Dial(*serverAddr, grpc.WithInsecure())
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
client := pb.NewRouteGuideClient(conn)
调用简单 RPC
feature, err := client.GetFeature(context.Background(), &pb.Point{Latitude: 409146138, Longitude: -746188906})
if err != nil {
log.Fatalf("%v.GetFeatures(_) = _, %v", client, err)
}
log.Println(feature)
调用服务端流式 RPC
rect := &pb.Rectangle{
Lo: &pb.Point{Latitude: 400000000, Longitude: -750000000},
Hi: &pb.Point{Latitude: 420000000, Longitude: -730000000},
}
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
for {
feature, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
log.Println(feature)
}
调用客户端流式 RPC
// 创建随机点流
points := []*pb.Point{
{Latitude: 407838351, Longitude: -746143763},
// 更多点...
}
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)
}
}
summary, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v", summary)
调用双向流式 RPC
stream, err := client.RouteChat(context.Background())
waitc := make(chan struct{})
// 接收协程
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
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)
}
}()
// 发送消息
notes := []*pb.RouteNote{
{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "First message"},
// 更多消息...
}
for _, note := range notes {
if err := stream.Send(note); err != nil {
log.Fatalf("Failed to send a note: %v", err)
}
}
stream.CloseSend()
<-waitc
总结
通过本教程,我们完整实现了一个基于 gRPC-Go 的路由指南服务,涵盖了:
- 使用 Protocol Buffers 定义服务接口
- 生成服务端和客户端代码
- 实现四种不同类型的 RPC 方法
- 构建完整的服务端和客户端应用
gRPC 的强大之处在于它提供了统一的跨语言 RPC 框架,同时支持多种通信模式。通过本教程的学习,你应该已经掌握了使用 gRPC-Go 开发服务的基本方法,可以开始构建自己的分布式应用了。
grpc-go 基于HTTP/2的gRPC的Go语言实现。 项目地址: https://gitcode.com/gh_mirrors/gr/grpc-go
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考