zoukankan      html  css  js  c++  java
  • Gonet2 游戏server框架解析之gRPC提高(5)

    上一篇blog是关于gRPC框架的基本使用,假设说gRPC仅仅是远程发几个參数,那和一个普通的http请求也没多大区别了。

    所以今天我就来学习一下gRPC高级一点的用法。

    流!

    流能够依据用法,分为单向和双向:

    • 单向
      – Client->Server
      – Server->Client
    • 双向
      – Client<=>Server

    以下是一个新的样例,三种服务分别使用了几种流服务方式:
    1. 參数表示一块地。而返回的是这块地上面的建筑。
    2. client不停的发送新的点。最后在服务端构成一个路径,返回。
    3. client发送新的点,服务端在做位置操作后返回新的点。

    syntax="proto3";
    
    message Point{}
    message Path{}
    message Ground{}
    message Construction{}
    
    service MapService {
        // Server Side Stream
        rpc ListConstructions(Ground) returns (stream Construction) {}
        // Client Side Stream
        rpc RecordPath(stream Point) returns (Path){}
        // Bidirectional streaming
        rpc Offset(stream Point) returns (stream Point){}
    }

    执行命令生成代码:

    protoc --go_out=plugins=grpc:. test.proto

    生成的代码太长了。一段一段帖吧,首先帖对象定义部分,这里应该略微简单:

    package test
    
    import proto "github.com/golang/protobuf/proto"
    
    import (
        context "golang.org/x/net/context"
        grpc "google.golang.org/grpc"
    )
    
    // Reference imports to suppress errors if they are not otherwise used.
    var _ = proto.Marshal
    
    type Point struct {
    }
    
    func (m *Point) Reset()         { *m = Point{} }
    func (m *Point) String() string { return proto.CompactTextString(m) }
    func (*Point) ProtoMessage()    {}
    
    type Path struct {
    }
    
    func (m *Path) Reset()         { *m = Path{} }
    func (m *Path) String() string { return proto.CompactTextString(m) }
    func (*Path) ProtoMessage()    {}
    
    type Ground struct {
    }
    
    func (m *Ground) Reset()         { *m = Ground{} }
    func (m *Ground) String() string { return proto.CompactTextString(m) }
    func (*Ground) ProtoMessage()    {}
    
    type Construction struct {
    }
    
    func (m *Construction) Reset()         { *m = Construction{} }
    func (m *Construction) String() string { return proto.CompactTextString(m) }
    func (*Construction) ProtoMessage()    {}
    
    // Reference imports to suppress errors if they are not otherwise used.
    var _ context.Context
    var _ grpc.ClientConn

    生成的Go语言的几种struct定义,正好相应了在proto文件里的4个message定义。对照上和篇中的样例,除了多几个对象。并没有更复杂。

    跳过!

    服务端

    刚一看到这段代码,高高我又有一点蒙。只是想想之前那句话,“相同的代码。细致看的话,认为难度是5,不细致看,一下就蒙了,那难度可能是8。”所以。根源不是难,而是懒得看。

    我在打这上面这段话的时候,发现了一段非常熟悉的代码,位于生成文件的最后一段,就是

    var _MapService_serviceDesc = grpc.ServiceDesc{}

    由于这段就是服务的名称与相应handler的映射嘛,所以。这一下子已经读懂了23行代码了。但有一点不同的是,这一次不是Method数组,而是Streams数组,非常明显,这一次的服务是流的形式。所以gRPC是把服务的方法名,作为流的名称。而为每个流,都相应的生成了一个Handler方法:

    • _MapService_ListConstructions_Handler
    • _MapService_RecordPath_Handler
    • _MapService_Offset_Handler

    经过上面的分析得出,大致的结构还是没有变化的。

    看生成代码最上面两段代码。已加凝视,就不多附文解释了,相信看过上一篇博客的朋友非常easy懂:

    // Server API for MapService service
    // 我们要实现的服务方法
    type MapServiceServer interface {
        ListConstructions(*Ground, MapService_ListConstructionsServer) error
        RecordPath(MapService_RecordPathServer) error
        Offset(MapService_OffsetServer) error
    }
    
    // 把我们实现的服务端对象实例,告诉gRPC框架
    func RegisterMapServiceServer(s *grpc.Server, srv MapServiceServer) {
        s.RegisterService(&_MapService_serviceDesc, srv)
    }
    • 服务方法一,Server Side单向流。
      顾名思义。服务端向client有一个单向的通道,入口在服务端,出口在client,而服务端自然会有一个向这个入口写数据的操作。

    // Server Side Stream
    // rpc ListConstructions(Ground) returns (stream Construction) {}
    func _MapService_ListConstructions_Handler(srv interface{}, stream grpc.ServerStream) error {
        m := new(Ground)
        if err := stream.RecvMsg(m); err != nil {
            return err
        }
        return srv.(MapServiceServer).ListConstructions(m, &mapServiceListConstructionsServer{stream})
    }
    
    type MapService_ListConstructionsServer interface {
        Send(*Construction) error
        grpc.ServerStream
    }
    
    type mapServiceListConstructionsServer struct {
        grpc.ServerStream
    }
    
    func (x *mapServiceListConstructionsServer) Send(m *Construction) error {
        return x.ServerStream.SendMsg(m)
    }

    首先_MapService_ListConstructions_Handler方法,在服务端接收到请求时调用,将数据解析出来,然后生成一个mapServiceListConstructionsServer,提供服务。


    mapServiceListConstructionsServer实现了MapService_ListConstructionsServer接口,包括了一个grpc封装好的ServerStream。这是通道的入口,和一个用于发送消息的Send方法。

    创建Server Side单向流服务:

    type mapServiceServer struct {
            ...
    }
    func (s *mapServiceServer) ListConstructions(ground *Ground, stream MapService_ListConstructionsServer) error {
        var constructions:= constructionsInGround(ground)
        for _, construction := range constructions {
            stream.Send(building)
        }
        return nil
    }
    • 服务方法二,Client Side单向流。
      相同在Client & Server之间有一条通道,而这一次服务端要接收client发来的Point数据。
    func _MapService_RecordPath_Handler(srv interface{}, stream grpc.ServerStream) error {
        return srv.(MapServiceServer).RecordPath(&mapServiceRecordPathServer{stream})
    }
    
    type MapService_RecordPathServer interface {
        SendAndClose(*Path) error
        Recv() (*Point, error)
        grpc.ServerStream
    }
    
    type mapServiceRecordPathServer struct {
        grpc.ServerStream
    }
    
    func (x *mapServiceRecordPathServer) SendAndClose(m *Path) error {
        return x.ServerStream.SendMsg(m)
    }
    
    func (x *mapServiceRecordPathServer) Recv() (*Point, error) {
        m := new(Point)
        if err := x.ServerStream.RecvMsg(m); err != nil {
            return nil, err
        }
        return m, nil
    }

    Handler用于接收client的请求。生成服务对象来做详细的处理。


    服务的定义包函一个流对象。接收方法,用来接收client不停传来的Point数据。最后返回路径,由于是一次性返回,因此命名为SendAndClose。

    创建Client Side单向流服务:

    func (s *mapServiceServer) RecordPath(stream MapService_RecordPathServer) error {
        for {
            point, err := stream.Recv()
            if err == io.EOF {
                return stream.SendAndClose(path)
            } else {
                path.append(point)
            }
        }
        return nil
    }
    • 双向流
    func _MapService_Offset_Handler(srv interface{}, stream grpc.ServerStream) error {
        return srv.(MapServiceServer).Offset(&mapServiceOffsetServer{stream})
    }
    
    type MapService_OffsetServer interface {
        Send(*Point) error
        Recv() (*Point, error)
        grpc.ServerStream
    }
    
    type mapServiceOffsetServer struct {
        grpc.ServerStream
    }
    
    func (x *mapServiceOffsetServer) Send(m *Point) error {
        return x.ServerStream.SendMsg(m)
    }
    
    func (x *mapServiceOffsetServer) Recv() (*Point, error) {
        m := new(Point)
        if err := x.ServerStream.RecvMsg(m); err != nil {
            return nil, err
        }
        return m, nil
    }

    经过上面对单向流和双向流的代码解读之后,这一部分,似乎是一看就懂了!


    来创建一个双向流的服务方法:

    func (s *mapServiceServer) Offset(stream MapService_OffsetServer) error {
        for {
            point, err := stream.Recv()
            if err == io.EOF {
                return nil
            }
            if err != nil {
                return err
            }
            offsetPoint := offset(point)
            if err := stream.Send(offsetPoint); err != nil {
                return err
            }
        }
    }

    和上一篇一样。这篇blog主要在于对生成代码的解析,以上代码。除了proto生成go文件是真实的,以下的代码我都没跑过。用番茄扔我吧!


    用了两篇Blog,基本对gRPC框架有了一个了解。下一篇,就要回到gonet2框架了!

    看看在框架里,是怎样使用gRPC的吧!

  • 相关阅读:
    114.114.114.114和8.8.8.8
    一台电脑双网卡同时上网
    eNSP模拟器
    路由器UPnP
    子网掩码
    网线水晶头制作
    AP (无线访问接入点(WirelessAccessPoint))
    筛选键
    注册表方法修改网络名称
    图片素材网址
  • 原文地址:https://www.cnblogs.com/llguanli/p/8412191.html
Copyright © 2011-2022 走看看