2019独角兽企业重金招聘Python工程师标准>>>
Grpc的简单使用心得
本文主要介绍一下grpc的环境搭建,并以一个简单的登录注册的例子来展示grpc的简单使用,更详细的内容请参考官方文档:
官方网站:https://grpc.io/
中文网站:http://doc.oschina.net/grpc?t=57966
一、环境搭建
1. 查看go version,go版本需要在1.6以上
2. 安装protobuf,地址:https://github.com/google/protobuf/releases,选择相应的版本,此处选用win32版本
下载解压,将解压好的文件中的bin目录添加到环境变量path中,方便以后调用protoc
3. 安装golang protobuf,执行命令:
go get -u github.com/golang/protobuf/proto // golang protobuf 库
go get -u github.com/golang/protobuf/protoc-gen-go // protoc --go_out 工具
4. 安装gRPC-go,执行命令:go get google.golang.org/grpc,可能会超时(被墙),所以可以去github上下载,地址: https://github.com/grpc/grpc-go,将克隆好的代码放到$GOPATH/src/google.golang.org目录下,修改文件名称gorpc-go为grpc
5. 安装genproto,执行命令:go get google.golang.org/genproto,可能会超时(被墙),所以可以去github上下载,地址:https://github.com/google/go-genproto,将克隆好的代码放到$GOPATH/src/google.golang.org目录下,修改文件名为genproto
二、运行内置demo
1. 进入$GOPATH/src/google.golang.org/grpc-go,/examples/helloworld,打开终端,执行命令:go run greeter_server/main.go搭建server端服务
2. 打开另一个终端,执行命令:go run greeter_client/main.go运行客户端服务,终端将打印信息,表示内置demo运行成功
三、案例编写
1. 定义服务与消息结构
项目结构:
包括三个package,client用于存放客户端代码,server用于存放服务端代码,demo用于存放.proto文件和.pb.go文件
首先我们需要使用protocol buffers去定义服务,包括定义service方法和request、response的结构(关于protocol buffers的语法,在此不详述,可参考相关文档:http://blog.csdn.net/u011518120/article/details/54604615)
新建demo.proto文件,在文件中用protobuf 3的语法定义服务和请求参数、响应参数结构:
// 开头必须申明protobuf的语法版本,此处申明采用proto3版本的语法
syntax = "proto3";
// 申明所属包
package demo;
// 定义请求参数结构
message Request {
string username = 1;
string password = 2;
}
// 定义响应参数结构
message Response {
string errno = 1;
string errmsg = 2;
}
message关键字用于定义消息格式,功能类似于go语言中的struct关键字。消息中的每一个参数都有对应的类型和参数名,以及标识号,像1、2、3这种。标识号的主要作用是为了在消息的二进制格式中识别各个参数,标识号范围是1~2^29-1,不能使用[19000-19999]范围内的标识符。
messge定义的消息,经过后续的protoc转换之后,对应于go文件中的请求参数(或响应参数)结构体定义
定义服务:
// 定义服务 service BasicService { rpc Login (Request) returns (Response) {} rpc Register (Request) returns (Response) {} }
service关键字用于定义一个服务,rpc用于定义服务处理函数,此处定义了两个服务处理函数,分别用于处理登录和注册的请求。函数第一个小括号中用于申明入参类型,第二个小括号用于申明出参。
service定义的服务,经过后续的protoc转换之后,对应于go文件中的服务接口定义
在demo.proto路径下,打开终端,运用proto命令,将.proto文件转换为.pb.go文件
输入命令:protoc --go_out=plugins=grpc:. demo.proto
将在当前路径下生成一个.pb.go的文件
.pb.go文件中的代码分为以下几个部分:
请求参数结构体定义,及其相应方法:
// 定义请求参数结构 type Request struct { Username string `protobuf:"bytes,1,opt,name=username" json:"username,omitempty"` Password string `protobuf:"bytes,2,opt,name=password" json:"password,omitempty"` } func (m *Request) Reset() { *m = Request{} } func (m *Request) String() string { return proto.CompactTextString(m) } func (*Request) ProtoMessage() {} func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *Request) GetUsername() string { if m != nil { return m.Username } return "" } func (m *Request) GetPassword() string { if m != nil { return m.Password } return "" }
响应参数结构体定义,及其相应方法:
// 定义响应参数结构 type Response struct { Errno string `protobuf:"bytes,1,opt,name=errno" json:"errno,omitempty"` Errmsg string `protobuf:"bytes,2,opt,name=errmsg" json:"errmsg,omitempty"` } func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *Response) GetErrno() string { if m != nil { return m.Errno } return "" } func (m *Response) GetErrmsg() string { if m != nil { return m.Errmsg } return "" }
初始化函数,注册请求参数结构体和响应参数结构体:
func init() { proto.RegisterType((*Request)(nil), "demo.Request") proto.RegisterType((*Response)(nil), "demo.Response") }
服务service对应的客户端API:
// Client API for BasicService service type BasicServiceClient interface { Login(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) Register(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) } type basicServiceClient struct { cc *grpc.ClientConn } func NewBasicServiceClient(cc *grpc.ClientConn) BasicServiceClient { return &basicServiceClient{cc} } func (c *basicServiceClient) Login(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { out := new(Response) err := grpc.Invoke(ctx, "/demo.BasicService/Login", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *basicServiceClient) Register(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { out := new(Response) err := grpc.Invoke(ctx, "/demo.BasicService/Register", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil }
服务service对应的服务端API:
// Server API for BasicService service type BasicServiceServer interface { Login(context.Context, *Request) (*Response, error) Register(context.Context, *Request) (*Response, error) } func RegisterBasicServiceServer(s *grpc.Server, srv BasicServiceServer) { s.RegisterService(&_BasicService_serviceDesc, srv) } func _BasicService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Request) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BasicServiceServer).Login(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/demo.BasicService/Login", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BasicServiceServer).Login(ctx, req.(*Request)) } return interceptor(ctx, in, info, handler) } func _BasicService_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Request) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BasicServiceServer).Register(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/demo.BasicService/Register", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BasicServiceServer).Register(ctx, req.(*Request)) } return interceptor(ctx, in, info, handler) } var _BasicService_serviceDesc = grpc.ServiceDesc{ ServiceName: "demo.BasicService", HandlerType: (*BasicServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Login", Handler: _BasicService_Login_Handler, }, { MethodName: "Register", Handler: _BasicService_Register_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "demo.proto", }
2. 编写服务端代码
新建demo_server.go文件,项目结构如图:
将demo_server.go的package改为main,定义一个空的结构体用于实现demo.pb.go中的BasicServiceServer
package main import ( "context" "practice/demo" ) // 用于实现BasicServiceServer type server struct {}
定义Login和Register方法用于实现BasicServiceServer:
Login方法:
// 定义Login方法,用于实现BasicServiceServer里面的Login方法 func (s *server) Login(ctx context.Context, req *demo.Request) (*demo.Response,error) { // req中封装了请求参数username和password if req.Username == "123" && req.Password == "123" { return &demo.Response{Errno:"0",Errmsg:"success"},nil } return &demo.Response{Errno:"1",Errmsg:"fail"},nil }
Register方法:
// 定义Register方法,用于实现BasicServiceServer里面的Register方法 func (s *server) Register(ctx context.Context, req *demo.Request) (*demo.Response,error) { if req.Username != "" && req.Password != "" { return &demo.Response{Errno:"0",Errmsg:"register success"},nil } return &demo.Response{Errno:"1",Errmsg:"register fail"},nil }
定义main函数,用于启动服务处理请求:
func main() { // 初始化log log.SetFlags(log.Llongfile|log.Ldate|log.Ltime) // tcp监听端口 listen,err := net.Listen("tcp", ":9999") if err != nil { log.Fatalf("listen localhost:9999 fail, err :%v\n", err) } // 新建服务 s := grpc.NewServer() demo.RegisterBasicServiceServer(s, &server{}) // 注册服务 reflection.Register(s) if err := s.Serve(listen); err != nil { log.Fatalf("serve fail, err :%v\n", err) } }
3. 编写客户端代码
新建demo_client.go文件,项目结构如图:
修改package为main,定义main函数:
package main import ( "google.golang.org/grpc" "log" "practice/demo" "context" "fmt" ) func main() { // 初始化log log.SetFlags(log.Llongfile|log.Ldate|log.Ltime) conn,err := grpc.Dial("localhost:9999", grpc.WithInsecure()) if err != nil { log.Fatalf("fail to connect localhost:9999, err :%v\n", err) } defer conn.Close() // 初始化客户端 client := demo.NewBasicServiceClient(conn) // 调用登录服务 resp,err := client.Login(context.Background(), &demo.Request{Username:"123", Password:"123"}) if err != nil { log.Fatalf("login err:%v\n",err) } fmt.Printf("login response:%v\n", resp) // 调用注册服务 resp,err = client.Register(context.Background(), &demo.Request{Username:"1235", Password:"1235"}) if err != nil { log.Fatalf("register err:%v\n",err) } fmt.Printf("register response:%v\n", resp) }
4. 运行服务
运行服务端程序,监听服务监听localhost:9999端口
运行客户端程序,调用服务,发起请求,打印响应:
在运行的过程中可能会出现下列错误
建议关掉防火墙之后重新试几次,应该就有了,网上对这一块的说明不多,我也遇到过几次问题,在重启了很多次服务之后就有用了,也不太清楚为什么会出现这个问题
四、案例总结
编写本案例的大致过程包括:首先,用proto buffer语法定义服务和消息;再通过protoc命令生成服务和消息对应的go代码;然后服务端代码的大致内容是实现服务的业务逻辑处理代码,并搭建服务,监听端口;客户端代码的大致内容是调用定义好的服务,内部将发起请求,与服务进行通信,返回响应;从外部来看,客户端调用服务就像调用自己的方法一样方便,而底层的通信则是交给了grpc去完成。因此只需要用proto buffer语法定义好服务和消息后,运用不同的命令就可以生成基于不同语言的服务和消息对应的代码,而他们之间的通信就像调用自己的方法一样方便,底层则全部交给了grpc去处理。