gRPC-Go 入门教程:从零开始构建路由指南服务

gRPC-Go 入门教程:从零开始构建路由指南服务

grpc-go 基于HTTP/2的gRPC的Go语言实现。 grpc-go 项目地址: https://gitcode.com/gh_mirrors/gr/grpc-go

本文将带你深入了解如何使用 gRPC-Go 框架构建一个完整的路由指南服务。我们将从基础概念讲起,逐步实现服务定义、代码生成、服务端和客户端开发等关键环节。

什么是 gRPC?

gRPC 是一个高性能、开源的通用 RPC 框架,由 Google 开发。它基于 HTTP/2 协议,使用 Protocol Buffers 作为接口定义语言(IDL)和消息交换格式。gRPC 的主要优势包括:

  1. 跨语言支持:服务端和客户端可以用不同语言实现
  2. 高效的二进制传输协议
  3. 支持四种通信模式
  4. 内置流式处理能力
  5. 强大的生态系统和工具链

项目概述

我们将构建一个路由指南服务,提供以下功能:

  • 获取特定位置的地理特征信息
  • 列出矩形区域内所有地理特征
  • 记录路线并计算汇总信息
  • 实时交换路线备注信息

服务定义

首先我们需要使用 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 的路由指南服务,涵盖了:

  1. 使用 Protocol Buffers 定义服务接口
  2. 生成服务端和客户端代码
  3. 实现四种不同类型的 RPC 方法
  4. 构建完整的服务端和客户端应用

gRPC 的强大之处在于它提供了统一的跨语言 RPC 框架,同时支持多种通信模式。通过本教程的学习,你应该已经掌握了使用 gRPC-Go 开发服务的基本方法,可以开始构建自己的分布式应用了。

grpc-go 基于HTTP/2的gRPC的Go语言实现。 grpc-go 项目地址: https://gitcode.com/gh_mirrors/gr/grpc-go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花琼晏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值