Gonet2 游戏服务器框架解析之gRPC入门(4)
2015-08-14 16:41
597 查看
Gonet2中,大量使用了gRPC,而对这个我不熟,所以这里花点时间了解一下。当然,环境我已经配好了,这里只是讲代码上如何使用,环境的搭建,网上应该蛮多。不过用gRPC要用科学的方式上网,这个对我华厦民族的同胞们,应该都不陌生了。
远程调用,一开始我想的很复杂,但是真的了解过之后,无非是,server side提供一个开方的接口,公开调用时传送数据的格式,client side遵照这种规定,调用接口提供的方法。
问题来了,既然是远程,那肯定是跨进程,甚至是跨计算机。所以可以通过网络传输的方式来远程调用。比如tcp/ip, http。
gRPC是远程调用框架的一种,传输是通过http2协议,使用接口描述语言 proto3 来定义服务接口,以及数据结构。之前有proto2的,不过proto3是最新的,语法结构有小小不同。
既然是有 A 调用 B 这个过程,有网络协议,那对于接口来说,就肯定会存在客户端,与服务端。服务端处理请求,客户端去调用接口。所以,废话了那么多,来看看一个最简单的hello world接口定义吧。
生成的代码,有点吓到我的感觉!我写了6行,生成这么多!先把完整代码帖上,然后我们一点点拆开看,要是怕吓到,先跳过。
仔细看一下,注释写的也挺清楚了。我个人的感觉是,打个比喻来说,同样的代码,仔细看的话,觉得难度是5,不仔细看,一下就蒙了,那难度可能是8。
上面分析过rpc调用,有服务端和客户端,还有通讯的数据格式,在这个生成分件里刚好有这三个部分。(其实,我是先看了这gRPC,再反过来分析理论的,反正说得通!)
第一步 - 定义并实现服务:
第二步 - 开启网络服务:
其实逻辑上,它跟一个http service也差不了多少啊,监听一个端口,然后注册服务,然后就等着收到请求啦。如果再仔细看一看Methods里面是一个数组,这里对Hello和_HelloService_Hello_Handle做了一个映射,应该是一个路由功能,说白了,就是根据请求的名字,调用相应的方法进行处理。
上面是生成的go语言代码,里面我自己加了注释。相信大家看过便懂,不懂的话是我表达的不好。接下来看看如何使用:
*注明一下,以上代码,除了proto是我自己生成了,下面的测试代码我都没跑过,说了是解析嘛。
远程调用,一开始我想的很复杂,但是真的了解过之后,无非是,server side提供一个开方的接口,公开调用时传送数据的格式,client side遵照这种规定,调用接口提供的方法。
问题来了,既然是远程,那肯定是跨进程,甚至是跨计算机。所以可以通过网络传输的方式来远程调用。比如tcp/ip, http。
gRPC是远程调用框架的一种,传输是通过http2协议,使用接口描述语言 proto3 来定义服务接口,以及数据结构。之前有proto2的,不过proto3是最新的,语法结构有小小不同。
既然是有 A 调用 B 这个过程,有网络协议,那对于接口来说,就肯定会存在客户端,与服务端。服务端处理请求,客户端去调用接口。所以,废话了那么多,来看看一个最简单的hello world接口定义吧。
// 没得更简单了,我试着用int32来做参数和返回值, // 可是结果确是必须为message类型. // message是grpc的封装类型,相当于go语言的struct或java的class. syntax="proto3"; message Req{} message Res{} service HelloService { rpc Hello(Req) returns (Res) {} }
生成的代码,有点吓到我的感觉!我写了6行,生成这么多!先把完整代码帖上,然后我们一点点拆开看,要是怕吓到,先跳过。
// Code generated by protoc-gen-go. // source: test.proto // DO NOT EDIT! /* Package test is a generated protocol buffer package. It is generated from these files: test.proto It has these top-level messages: Req Res */ 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 Req struct { } func (m *Req) Reset() { *m = Req{} } func (m *Req) String() string { return proto.CompactTextString(m) } func (*Req) ProtoMessage() {} type Res struct { } func (m *Res) Reset() { *m = Res{} } func (m *Res) String() string { return proto.CompactTextString(m) } func (*Res) ProtoMessage() {} // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // Client API for HelloService service type HelloServiceClient interface { Hello(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error) } type helloServiceClient struct { cc *grpc.ClientConn } func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) Hello(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error) { out := new(Res) err := grpc.Invoke(ctx, "/.HelloService/Hello", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } // Server API for HelloService service type HelloServiceServer interface { Hello(context.Context, *Req) (*Res, error) } func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { s.RegisterService(&_HelloService_serviceDesc, srv) } func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { in := new(Req) if err := codec.Unmarshal(buf, in); err != nil { return nil, err } out, err := srv.(HelloServiceServer).Hello(ctx, in) if err != nil { return nil, err } return out, nil } var _HelloService_serviceDesc = grpc.ServiceDesc{ ServiceName: ".HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Hello", Handler: _HelloService_Hello_Handler, }, }, Streams: []grpc.StreamDesc{}, }
仔细看一下,注释写的也挺清楚了。我个人的感觉是,打个比喻来说,同样的代码,仔细看的话,觉得难度是5,不仔细看,一下就蒙了,那难度可能是8。
上面分析过rpc调用,有服务端和客户端,还有通讯的数据格式,在这个生成分件里刚好有这三个部分。(其实,我是先看了这gRPC,再反过来分析理论的,反正说得通!)
服务端
先看一下Server部分,我加了一些注释,更详细的解释了每一部分的作用,完全是照顾新手,还有跟我一样,不喜欢仔细看代码的。// HelloService服务接口定义 // 通过实现这个接口,来定义服务方法Hello的具体内容,这就是服务端(Server Side)了 type HelloServiceServer interface { Hello(context.Context, *Req) (*Res, error) } // 实现了上面的服务端(Server Side) // 需要调用这个方法来告诉gRPC框架,我们的服务端对象是谁。 func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { s.RegisterService(&_HelloService_serviceDesc, srv) } // 这个是服务接口的代理,就是加了一层封装的意思, // 网络传来的数据是byte[],在这里解析成了一个Req对象, // 然后才去调用我们server side的Hello方法。 func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { in := new(Req) if err := codec.Unmarshal(buf, in); err != nil { return nil, err } out, err := srv.(HelloServiceServer).Hello(ctx, in) if err != nil { return nil, err } return out, nil } // 这个变量是服务的描述对象。定义了服务的名字,类型,方法等。 // 在RegisterHelloServiceServer方法中,就是把这个变量传给了gRPC框架。 var _HelloService_serviceDesc = grpc.ServiceDesc{ ServiceName: ".HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{//服务方法数组 { MethodName: "Hello", Handler: _HelloService_Hello_Handler, }, }, Streams: []grpc.StreamDesc{}, }
第一步 - 定义并实现服务:
type helloServiceServer struct { } func (s *helloServiceServer) Hello(ctx context.Context, req *test.Req) (*test.Res, error) { fmt.Println("Hello") return &test.Res{}, nil }
第二步 - 开启网络服务:
// 监听端口 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } // 创建grpc实例 grpcServer := grpc.NewServer() // 注册helloService服务 test.RegisterHelloServiceServer(grpcServer, &helloServiceServer{}) // 启动服务 grpcServer.Serve(lis)
其实逻辑上,它跟一个http service也差不了多少啊,监听一个端口,然后注册服务,然后就等着收到请求啦。如果再仔细看一看Methods里面是一个数组,这里对Hello和_HelloService_Hello_Handle做了一个映射,应该是一个路由功能,说白了,就是根据请求的名字,调用相应的方法进行处理。
客户端
// 客户端接口,里面定义了服务的方法 type HelloServiceClient interface { Hello(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error) } // 这是客户端(Client side)的实现 type helloServiceClient struct { cc *grpc.ClientConn } // 工厂方法,获得一个客户端实例。 // 我们的代码要通过这个实例来调用远程服务。 func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { return &helloServiceClient{cc} } // 客户端(Client side)的服务方法封装。 // 我们调用这个方法,在这里gRPC把我们的参数封装好,然后发送出去。 // 作为客户端,只管调用就完事了,是不是很方便! func (c *helloServiceClient) Hello(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error) { out := new(Res) err := grpc.Invoke(ctx, "/.HelloService/Hello", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil }
上面是生成的go语言代码,里面我自己加了注释。相信大家看过便懂,不懂的话是我表达的不好。接下来看看如何使用:
// 先连接到服务器 conn, err := grpc.Dial(*serverAddr) if err != nil { ... } defer conn.Close() // 通过工厂方法得到一个客户端对象 client := test.NewHelloServiceClient(conn) // 调用 client.Hello(context.Background(), &test.Req{})
*注明一下,以上代码,除了proto是我自己生成了,下面的测试代码我都没跑过,说了是解析嘛。
相关文章推荐
- Yandex.Algorithm Online Round 3 Sunday, June 15, 2014
- POJ 3274 Gold Balanced Lineup
- 每日一题(4)——动态规划《Introduction to Algorithms》总结篇
- Django Models的数据类型 汇总
- HDU 3820 Golden Eggs( 最小割 奇特建图)经典
- Go语言阅读小笔记,来自知呼达达关于unsafe.Pointer的分享.
- 天天有人说电信欢go与联通合并,然并卵
- Codeforces 570 B. Simple Game ( 概率 )
- EGO Refresh小总结
- Codeforces 570 A. Elections
- 2016年Google面筋记录
- 去大公司还是去小公司工作——要进大公司的核心部门(提升视野,锻炼技能),远离没真本事的小公司,要自我驱动 good
- Django-模型
- web框架django安装
- VIEWGOOD(远古)多媒体系统在电信行业的应用
- Go 1.6 GC improvements 转自google doc
- Django 学习笔记(二) 《models》
- 【转】The Google File System 中文版
- The last packet sent successfully to the server was 0 milliseconds ago, The driver has not received
- OC中protocol、category和继承的区别