【golang】10、grpc server 和 client、proto

本文包括如下部分:

  1. 定义proto文件
  2. 编译出各编程语言代码
  3. go的grpc程序

在这里插入图片描述

用idl定义的协议, 可用protoc工具生成不同编程语言实现的此协议

协议包括, 从proto buffers反序列化到struct, 和将struct序列化到proto buffers

client可以像在local一样调用remote的server

我们定义service, 及其methods, 在client有grpc的stub实现了定义的methods

grpc的优势

我们的示例是一个简单的路径映射应用程序,它允许客户端获取有关其路径上的要素的信息,创建其路径的摘要,并与服务器和其他客户端交换路径信息,如交通更新。
方便: 可跨端,跨语言通信, grpc会处理底层的一切
高效: protobuf序列化格式

下文以如下示例code讲解

git clone -b v1.46.0 --depth 1 https://github.com/grpc/grpc-go
cd grpc-go/examples/route_guide

定义proto文件

service RouteGuide {
    
}

然后在其中定义rpc方法

  • 如下是client向server的同步调用
rpc GetFeature(Point) returns (Feature) {}
  • 单向stream调用: 如下是client向server发请求, 获取到stream. client迭代stream直到无消息. 其server端定义如下
// 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) {}
  • 双向stream调用: 双端各自都发送1个read-write stream, 2个流独立工作
// 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) {}

proto文件同时可定义protobuf数据结构

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

生成client和server code

protoc -I=./ --go_out=plugins=grpc:./ ./*.proto 即可生成go语言代码,详见 proto 文档

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    routeguide/route_guide.proto

即可在route_guide.pb.go中定义message, 在route_guide_grpc.pb.go 中定义 client需实现的interface, server需实现的interface

创建server

实现server的interface

创建一个实现service的interface的server, 并启动server使listen到client.

type routeGuideServer struct {
        ...
}
...

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
        ...
}
...

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}
...

最简单的同步rpc方法

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
}

server的stream rpc方法

server端将数据通过Send()发送

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
}

client的stream rpc方法

下例中, 客户端的流方法Recordroute,其中我们从客户端获得一个点流,并返回一个包含他们行程信息的RouteSummary.
该stream参数可Recv(),可SendAndClose()
若收到io.EOF则说明server已传输完毕

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

双向rpc

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)
                ... // look for notes to be sent to client
    for _, note := range s.routeNotes[key] {
      if err := stream.Send(note); err != nil {
        return err
      }
    }
  }
}

启动server

实现完方法后, 启动一个grpc server

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
...
grpcServer := grpc.NewServer(opts...)
pb.RegisterRouteGuideServer(grpcServer, newServer())
grpcServer.Serve(lis)

指定端口号, 创建grpc的server, 注册我们的server实现,调serve()方法阻塞等待

创建client

创建一个grpc的channel, 用于和server连接, 其中dialOption(可选)可设置鉴权参数(如TLS,GCE证书,JWT证书等)

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

再建立stub来执行rpc

client := pb.NewRouteGuideClient(conn)

在GRPC-GO中,RPC以阻塞/同步模式运行,这意味着RPC调用等待服务器响应,并将返回响应或错误。

最简单的rpc方法

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

client调用server端的stream 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)
}

client端的stream rpc

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

client端的双向stream rpc

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

参考

go grpc的基本概念

server 和 client 示例

在这里插入图片描述

### 回答1: GRPC 默认使用 50051 端口。然而,您可以指定其他端口来运行 GRPC 服务。您可以使用 --port 选项指定端口,例如: ``` grpc_server --port=9090 ``` 这将在端口 9090 上运行 GRPC 服务。 GRPC 客户端连接到服务器时,需要知道服务器使用的端口。因此,您需要将端口信息包含在服务器地址中,例如: ``` grpc_client --server_address=localhost:9090 ``` 这将连接到在本地主机的端口 9090 上运行的 GRPC 服务。 ### 回答2: gRPC 占用了两个端口。其中一个是用于 gRPC 服务器监听客户端请求的端口,通常是 50051;另一个是用于 gRPC 服务器上的服务器反向调用客户端的端口,通常是客户端指定的一个可用端口。 在 TCP/IP 协议中,一个端口是通过数字来标识的,其范围是从 0 到 65535。其中,0 到 1023 之间的端口被称为“知名端口”(Well-Known Ports),是预留给某些常见的网络服务的,比如 HTTP 服务使用的端口号是 80,HTTPS 则是使用 443。而 gRPC 使用的端口号是 50051,不属于知名端口中的一个。 gRPC 客户端可以选择在连接到服务器时指定一个自己可用的端口号来处理服务器的反向调用。如果不指定,默认会选择一个可用的端口号。 通过以上的说明可以看出,gRPC 占用了两个端口,一个用于客户端发送请求,一个用于服务器反向调用客户端。这样的设计使得 gRPC 可以实现双向的、高效的通信。 ### 回答3: GRPC 默认使用1个端口。GRPC 是一种高性能、开源的远程过程调用(RPC)框架,允许客户端和服务器之间进行双向通信。在默认情况下,GRPC 使用一个端口,该端口用于客户端与服务器之间的通信。通常情况下,GRPC 使用TCP 或者 HTTP/2 协议来进行数据交换。GRPC 默认情况下使用的是 50051 端口,但实际上可以通过配置来使用不同的端口号。此外,GRPC 还支持多路复用技术,可以同时处理多个并发的请求和响应,提高了性能和效率。总之,GRPC 只需要一个端口来处理客户端和服务器之间的通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呆呆的猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值