您的位置:首页 > Web前端

Grpc的简单使用心得

2018-01-09 00:00 417 查看
摘要: 本文主要介绍一下grpc的环境搭建,并以一个简单的登录注册的例子来展示grpc的简单使用

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 ""
}

初始化函数,注册请求参数结构体和响应参数结构体:

funcinit() {
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去处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  GRPC Go Protocol Buffers