Gonet2 遊戲server框架解析之gRPC提升(5)

上一篇blog是關於gRPC框架的基本使用,假設說gRPC僅僅是遠程發幾個參數,那和一個普通的http請求也沒多大區別了。css

因此今天我就來學習一下gRPC高級一點的用法。git

流!github

流可以依據用法,分爲單向和雙向:golang

  • 單向
    – 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){}
}

執行命令生成代碼:markdown

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

生成的代碼太長了。一段一段帖吧,首先帖對象定義部分,這裏應該略微簡單:app

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定義。對照上和篇中的樣例,除了多幾個對象。並無更復雜。框架

跳過!ide

服務端

剛一看到這段代碼,高高我又有一點蒙。只是想一想以前那句話,「相同的代碼。細緻看的話,認爲難度是5,不細緻看,一下就蒙了,那難度多是8。」因此。根源不是難,而是懶得看。post

我在打這上面這段話的時候,發現了一段很是熟悉的代碼,位於生成文件的最後一段,就是

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的吧!

相關文章
相關標籤/搜索