上一篇blog是关于gRPC框架的基本使用,假设说gRPC仅仅是远程发几个參数,那和一个普通的http请求也没多大区别了。css
因此今天我就来学习一下gRPC高级一点的用法。git
流!github
流可以依据用法,分为单向和双向:golang
如下是一个新的样例,三种服务分别使用了几种流服务方式:
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方法:
通过上面的分析得出,大体的结构仍是没有变化的。
看生成代码最上面两段代码。已加凝视,就很少附文解释了,相信看过上一篇博客的朋友很是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 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
}
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的吧!