gRPC(1):入门及简单使用(go) 中,我们实现了一个简单的 gRPC 应用程序,其中双方通信是简单的请求—响应模式,没发出一个请求都会得到一个响应,然而,借助 gRPC 可以实现不同的通信模式,这里介绍四种 gRPC 应用程序的基础通信模式:一元RPC、服务端流RPC、客户端流RPC、双向流RPC

1、一元RPC

一元 RPC 也被称为简单 RPC, 其实就是 gRPC(1):入门及简单使用(go) 中实现的请求—响应模式,每调用一次得到一个结果,这里再以一个简单的订单管理程序做说明,实现两个服务:addOrder 用于添加订单;getOrder 用于根据 id 获取订单:

  • 服务定义
syntax = "proto3";
package proto;
option go_package = "./proto"; service OrderManagement {
rpc addOrder(Order) returns (StringValue);
rpc getOrder(StringValue) returns (Order);
} message Order {
string id = 1;
repeated string items = 2; // repeated 表示列表
string description = 3;
float price = 4;
string destination = 5;
} message StringValue {
string value = 1;
}
  • 服务端实现
package main

import (
"context"
"fmt"
"log"
"net"
"strings" pb "order/proto" "github.com/gofrs/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) const (
port = ":50051"
) type server struct {
pb.UnimplementedOrderManagementServer
} // 模拟存储
var orderMap = make(map[string]*pb.Order) func (s *server) AddOrder(ctx context.Context, order *pb.Order) (*pb.StringValue, error) {
id, err := uuid.NewV4()
if err != nil {
return nil, status.Errorf(codes.Internal, "Error while generating Product ID", err)
}
order.Id = id.String()
orderMap[order.Id] = order
log.Printf("Order %v : %v - Added.", order.Id, order.Description)
return &pb.StringValue{Value: order.Id}, nil
} func (s *server) GetOrder(ctx context.Context, orderID *pb.StringValue) (*pb.Order, error) {
order, exists := orderMap[orderID.Value]
if exists && order != nil {
log.Printf("Order %v : %v - Retrieved.", order.Id, order.Description)
return order, nil
}
return nil, status.Errorf(codes.NotFound, "Order does not exist.", orderID.Value)
} func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterOrderManagementServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
  • 客户端实现
package main

import (
"context"
"io"
"log"
"time" pb "order/proto" "google.golang.org/grpc"
) const (
address = "localhost:50051"
) func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close() c := pb.NewOrderManagementClient(conn) orderID, err := c.AddOrder(context.Background(),
&pb.Order{
Items: []string{"XiaoMI 11"},
Description: "XiaoMI 11",
Price: 3999,
Destination: "suzhou",
})
if err != nil {
log.Fatalf("could not add order: %v", err)
}
log.Printf("Added order: %v", orderID.Value)
}

2、服务端流RPC

与一元 RPC 不同的是,流模式下响应或者请求都可以是一个序列,这个序列也被称为”流“,服务端流 RPC 下,客户端发出一个请求,但不会立即得到一个响应,而是在服务端与客户端之间建立一个单向的流,服务端可以随时向流中写入多个响应消息,最后主动关闭流,而客户端需要监听这个流,不断获取响应直到流关闭

下面以一个简单的关键词搜索功能为例,客户端发送关键字,服务端进行匹配,每找到一个就写进流中,在之前的基础上添加代码:

  • 服务定义
service OrderManagement {
...
// stream 将返回参数指定为订单流
rpc searchOrders(StringValue) returns (stream Order);
}
  • 服务端实现
func (s *server) SearchOrders(searchQuery *pb.StringValue, stream pb.OrderManagement_SearchOrdersServer) error {
for key, order := range orderMap {
for _, item := range order.Items {
if strings.Contains(item, searchQuery.Value) {
err := stream.Send(&order)
if err != nil {
return fmt.Errorf("error sending message to stream: %v", err)
}
log.Printf("order found: " + key)
break
}
}
}
return nil
}
  • 客户端实现
...
// 获得建立的流对象
stream, err := c.SearchOrders(context.Background(), &pb.StringValue{Value: "XiaoMI"})
if err != nil {
log.Fatalf("search error: %v", err)
}
for {
// 循环读取
order, err := stream.Recv()
if err == io.EOF {
log.Print("EOF")
break
}
if err != nil {
log.Fatal("error: ", err)
}
log.Print(order)
}

3、客户端流RPC

客户端流,和服务端流一样的道理,只不过流的方向变为从客户端到服务端,可以发送多条响应,服务端只会响应一次,但何时响应取决于服务端的逻辑,以更新订单序列为例,客户端可以发送一系列订单,服务端可以选择在任意时候停止读取并发送响应:

  • 服务定义
service OrderManagement {
...
rpc updateOrders(stream Order) returns (StringValue);
}
  • 服务端实现
func (s *server) UpdateOrders(stream pb.OrderManagement_UpdateOrdersServer) error {
for {
order, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.StringValue{Value: "finished"})
}
if err != nil {
return err
}
orderMap[order.Id] = order
log.Print("OrderID " + order.Id + " updated")
}
}
  • 客户端实现
// 取得流
updateStream, err := c.UpdateOrders(context.Background())
if err != nil {
log.Fatalf("update err: %v", err)
}
// 发送 Order1
if err = updateStream.Send(&pb.Order{
Id: "1",
Items: []string{"Huawei P50"},
Description: "Huawei P50",
Price: 5999,
Destination: "suzhou",
}); err != nil {
log.Fatalf("send error: %v", err)
}
// 发送 Order2
if err = updateStream.Send(&pb.Order{
Id: "2",
Items: []string{"iphone 12"},
Description: "iphone 12",
Price: 8999,
Destination: "suzhou",
}); err != nil {
log.Fatalf("send error: %v", err)
}
...
// 关闭流,结束发送
updateRes, err := updateStream.CloseAndRecv()
if err != nil {
log.Fatalf("update stream close error: %v", err)
}
log.Printf("update res: %v", updateRes)

4、双向流RPC

双向流,顾名思义,由客户端发起调用后,将建立起双向的流,在这之后,通信将完全基于双方的应用逻辑,流的操作完全独立,客户端和服务端可以按照任意顺序进行读取和写入,以一个订单筛选过程为例,客户端发送一串订单 ID 序列,服务端进行检查,每遇到一个有效的 ID 就写入流中响应:

  • 服务定义
service OrderManagement {
...
rpc processOrders(stream StringValue) returns (stream StringValue);
}
  • 服务端实现
func (s *server) ProcessOrders(stream pb.OrderManagement_ProcessOrdersServer) error {
for {
orderId, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
order, exists := orderMap[orderId.Value]
if exists && order != nil {
stream.Send(&pb.StringValue{Value: order.Id})
}
}
}
  • 客户端实现
...
// 取得双向流
processStream, err := c.ProcessOrders(context.Background())
// 同步channel,防止主程序提前退出
waitc := make(chan struct{})
// 双向流是完全异步的,开一个协程用于读取响应
go func() {
for {
orderId, err := processStream.Recv()
if err == io.EOF {
close(waitc)
return
}
if err != nil {
log.Fatalf("recv error: %v", err)
}
log.Print("recv " + orderId.Value)
}
}()
// 请求
if err = processStream.Send(&pb.StringValue{Value: "1"}); err != nil {
log.Fatalf("1 send error: %v", err)
}
if err = processStream.Send(&pb.StringValue{Value: "2"}); err != nil {
log.Fatalf("2 send error: %v", err)
}
if err = processStream.Send(&pb.StringValue{Value: "3"}); err != nil {
log.Fatalf("3 send error: %v", err)
}
if err = processStream.CloseSend(); err != nil {
log.Fatal(err)
}
// 等待读取结束
<-waitc

这就是 gRPC 中主要的四种通信模式,基于它们可以实现各种 gRPC 场景下的交互,至于选择哪种,还需根据具体的场景考虑

gRPC(2):四种基本通信模式的更多相关文章

  1. Activity有四种加载模式(转)

    Activity有四种加载模式: standard singleTop singleTask singleInstance 在多Activity开发中,有可能是自己应用之间的Activity跳转,或者 ...

  2. 活动 Activity 四种加载模式

    singleTop要求如果创建intent的时候栈顶已经有要创建的Activity的实例,则将intent发送给该实例,而不发送给新的实例.(注意是栈顶,不在栈顶照样创建新实例!) singleTas ...

  3. 【Android进阶】Activity的四种加载模式

    Activity的四种加载模式: 1.standard :系统的默认模式,一次跳转即会生成一个新的实例.假设有一个activity命名为Act1, 执行语句:startActivity(new Int ...

  4. android中的LaunchMode详解----四种加载模式

    Activity有四种加载模式: standard singleTop singleTask singleInstance 配置加载模式的位置在AndroidManifest.xml文件中activi ...

  5. 区分Activity的四种加载模式

    在多Activity开发中,有可能是自己应用之间的Activity跳转,或者夹带其他应用的可复用Activity.可能会希望跳转到原来某个Activity实例,而不是产生大量重复的Activity. ...

  6. activity的四种加载模式介绍

      四种加载模式的介绍: a) Standard : 系统默认模式,一次跳转即会生成一个新的实例:    b) SingleTop : 和 standard 类似,唯一的区别就是当跳转的对象是位于栈顶 ...

  7. Android学习记录(8)—Activity的四种加载模式及有关Activity横竖屏切换的问题

    Activity有四种加载模式:standard(默认), singleTop, singleTask和 singleInstance.以下逐一举例说明他们的区别: standard:Activity ...

  8. 区分Activity的四种加载模式【转载】

    此文为转载,文章来源:http://marshal.easymorse.com/archives/2950 文章作者:   Marshal's Blog 参考文章:http://blog.csdn.n ...

  9. 四种软件开发模式:tdd、bdd、atdd和ddd的概念

    看一些文章会看到TDD开发模式,搜索后发现有主流四种软件开发模式,这里对它们的概念做下笔记. TDD:测试驱动开发(Test-Driven Development) 测试驱动开发是敏捷开发中的一项核心 ...

随机推荐

  1. PSP初体验:求交点

    项目 内容 课程:北航2020春软件工程 博客园班级博客 作业:完成一个平面图形求交点的程序,体验PSP的过程 个人项目作业 我在这个课程的目标是 体验软件开发的全流程 这个作业在哪个具体方面帮助我实 ...

  2. 008.kubernets的调度系统之标签选择器

    一 Kubernetes 调度简介 除了让 kubernetes 集群调度器自动为 pod 资源选择某个节点(默认调度考虑的是资源足够,并且 load 尽量平均),有些情况我们希望能更多地控制 pod ...

  3. shell基础之pxe批量部署

    通过安装及配置DHCP,tftp-server,xinetd,httpd,syslinux来实现批量安装Linux系统 #!/bin/bash #检查环境 se_state=`getenforce` ...

  4. ifconfig显示的网卡信息和我的配置文件名不符

    比如我的配置文件, cd /etc/sysconfig/network-scripts/ifcfg-Auto_eth0是这个名称,但是我使用ifconfig显示的信息却是 eth6   Link en ...

  5. redis的rehash过程

    在扩容和收缩的时候,如果哈希字典中有很多元素,一次性将这些键全部rehash到ht[1]的话,可能会导致服务器在一段时间内停止服务.所以,采用渐进式rehash的方式,详细步骤如下: 为ht[1]分配 ...

  6. [LeetCode] 1074. 元素和为目标值的子矩阵数量

    矩阵前缀和.因为矩阵中可能包含负值,所以这题肯定不会存在什么剪枝,动态规划的可能性.所以这个题也就没什么弯弯绕绕.个人感觉算不上个Hard题目. 最直观的思路就是枚举子矩阵,既枚举矩阵的左上角节点和右 ...

  7. Step By Step(Lua系统库)

    Step By Step(Lua系统库) Lua为了保证高度的可移植性,因此,它的标准库仅仅提供了非常少的功能,特别是和OS相关的库.但是Lua还提供了一些扩展库,比如Posix库等.对于文件操作而言 ...

  8. 5分钟就能学会的简单结构 | MLP-Mixer: An all-MLP Architecture for Vision | CVPR2021

    文章转自:微信公众号「机器学习炼丹术」 作者:炼丹兄(欢迎交流,共同进步) 联系方式:微信cyx645016617 论文名称:「MLP-Mixer: An all-MLP Architecture f ...

  9. Sql server 多列去重复值,相同的只显示一条数据

    CREATE TABLE #tp( headerNo VARCHAR(10), machineNO VARCHAR(10), descrption nVARCHAR(20), artNo VARCHA ...

  10. 对标 Spring Boot & Cloud ,轻量框架 Solon 1.4.12 发布

    Solon 是一个轻量的Java基础开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Job.Micro service.WebS ...