导语: 本文以幽默诙谐的方式,介绍gRPC的4种client-server服务模式的开发实践及应用场景
前言:为什么要写这篇文章?
gRPC client-server服务模式
这里先将重要的结论写出来,方便以后查阅,具体介绍见下文。找不到准确的中文来翻译这几种模式,就保留了英文。
应用场景:
应用场景:
应用场景:
接下来就让我用一个男女生之间的交往故事来说明这4种服务模式。内容纯属虚构,并故意写得比较搞笑。如果有不恰当的描述,请告诉我。
男女生交往之gRPC的4种模式
一些说明
基础代码说明
// 建立跟server的连接 conn, err := grpc.Dial(love_const.Address, grpc.WithInsecure())
(左滑可查看完整代码,下同)
// 设置监听协议与端口lis, err := net.Listen("tcp", love_const.Address)
// 初始化gRPC server实例 grpcServer := grpc.NewServer() // 注册命令字与处理函数 love_proto.RegisterBehaviorServer(grpcServer, newServer())
// 启动服务 grpcServer.Serve(lis)
0x1: A simple RPC
Selina: "在哪"Jhon: "刚起床,在打游戏呢"
这种模式就是我们用得最多的模式,一发一收
response, err := client.WhereAreYou(ctx, message)
func (s *loveServer) WhereAreYou(ctx context.Context, message *love_proto.Message) (*love_proto.Response, error) { rsp := new(love_proto.Response) rsp.Words = "刚起床,在打游戏呢" return rsp, nil}
0x2: A client-to-server streaming RPC
Selina: "你在干嘛"Selina: "我不开心"Selina: "我要和你说话"Selina: "你怎么还不打电话过来"Selina: "我来大姨妈了"Jhon: "哦,多喝热水"
这种模式是client先建立长连接,然后发送多个request给server,server最后统一回一个rsp。
应用场景
// 获取一个stream对象 stream, err := client.ContinuousCall(ctx) if err != nil { log.Fatalf("%v.ContinuousCall(_) = _, %v", client, err) } // 通过stream来发送多个message for _, message := range messages { fmt.Printf("message words: %s\n", message.Words) if err := stream.Send(message); err != nil { log.Fatalf("%v.Send(%v) = %v", stream, message, err) } } // 发送EOF给server,然后收取server的回包 reply, err := stream.CloseAndRecv() if err != nil { log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) }
for { // 不断收取client的发包 message, err := stream.Recv() // 当客户端发送EOF时说明要给回包了 if err == io.EOF { rsp := new(love_proto.Response) rsp.Words = "哦,多喝热水" return stream.SendAndClose(rsp) } if err != nil { return err } printGirlWords(message.Words) }
0x3: A server-to-client streaming RPC
Selina: "这么久不给我打电话,你是不是不爱我了?"Jhon: "啊,宝贝你怎么了?"Jhon: "我刚刚在玩游戏,那一局刚开,我走不开啊"Jhon: "你不能不讲道理啊"Jhon: "你都20分钟不回我了"Jhon: "好了,宝贝,我错了,都是我的错"Jhon: "你在家等我,我过去接你,带你去买包包"
这种模式是client先发一个请求到server,然后server不停的回包
应用场景
// 获取stream对象,并发送一个消息 stream, err := client.LoveOrNot(ctx, message) if err != nil { log.Fatalf("%v.LoveOrNot(_) = _, %v", client, err) } for { // 不断收取server回包 response, err := stream.Recv() // EOF表示server发包结束 if err == io.EOF { break } if err != nil { log.Fatalf("%v.LoveOrNot(_) = _, %v", client, err) } log.Printf("rsp: %s", response.Words) }
for _, response := range responses { // 不停向client发消息 if err := stream.Send(response); err != nil { return err } }
0x4: A Bidirectional streaming RPC
Selina: "好呀好呀"Jhon: "宝贝我很快就到啦"Selina: "等我化妆"Jhon: "宝贝我很快就到啦"Selina: "亲爱的你最好啦"Jhon: "宝贝我很快就到啦"Selina: "么么哒"Jhon: "宝贝我很快就到啦"
这种模式就是建立一个长连接,然后client和server可以随意收发数据
应用场景
// 获取stream对象 stream, err := client.LoveChat(ctx)if err != nil { log.Fatalf("%v.LoveChat(_) = _, %v", client, err) } // 这个管道没有太多实际意义,只是为了让client将rsp都收完整 waitc := make(chan struct{}) go func() { for { // 不停地收server的包 response, err := stream.Recv() // server回包EOF,然后关闭管道 if err == io.EOF { // read done. close(waitc) log.Printf("EOF return") return } if err != nil { log.Fatalf("Failed to receive a note : %v", err) } log.Printf("rsp: %s", response.Words) } }() // 每隔一秒向server发一个消息,模拟聊天场景 for _, message := range messages { time.Sleep(1 * time.Second) if err := stream.Send(message); err != nil { log.Fatalf("Failed to send a message: %v", err) } } // 这里告诉server: client已经将数据发完了(其实就是给server发一个EOF),server会返回一个io.EOF stream.CloseSend() <-waitc
for { // 不断收取client发包 message, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err }
for _, response := range responses { // 给client发包 if err := stream.Send(response); err != nil { return err } } }
男生们看过来,看过来