本文主要介紹一下grpc的環境搭建,並以一個簡單的登陸註冊的例子來展現grpc的簡單使用,更詳細的內容請參考官方文檔:git
官方網站:https://grpc.io/github
中文網站:http://doc.oschina.net/grpc?t=57966golang
1. 查看go version,go版本須要在1.6以上json
2. 安裝protobuf,地址:https://github.com/google/protobuf/releases,選擇相應的版本,此處選用win32版本tcp
下載解壓,將解壓好的文件中的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 工具google
4. 安裝gRPC-go,執行命令:go get google.golang.org/grpc,可能會超時(被牆),因此能夠去github上下載,地址: https://github.com/grpc/grpc-go,將克隆好的代碼放到$GOPATH/src/google.golang.org目錄下,修改文件名稱gorpc-go爲grpcspa
5. 安裝genproto,執行命令:go get google.golang.org/genproto,可能會超時(被牆),因此能夠去github上下載,地址:https://github.com/google/go-genproto,將克隆好的代碼放到$GOPATH/src/google.golang.org目錄下,修改文件名爲genproto
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運行成功
項目結構:
包括三個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關鍵字。消息中的每個參數都有對應的類型和參數名,以及標識號,像一、二、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", }
新建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) } }
新建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) }
運行服務端程序,監聽服務監聽localhost:9999端口
運行客戶端程序,調用服務,發起請求,打印響應:
在運行的過程當中可能會出現下列錯誤
建議關掉防火牆以後從新試幾回,應該就有了,網上對這一塊的說明很少,我也遇到過幾回問題,在重啓了不少次服務以後就有用了,也不太清楚爲何會出現這個問題
編寫本案例的大體過程包括:首先,用proto buffer語法定義服務和消息;再經過protoc命令生成服務和消息對應的go代碼;而後服務端代碼的大體內容是實現服務的業務邏輯處理代碼,並搭建服務,監聽端口;客戶端代碼的大體內容是調用定義好的服務,內部將發起請求,與服務進行通訊,返回響應;從外部來看,客戶端調用服務就像調用本身的方法同樣方便,而底層的通訊則是交給了grpc去完成。所以只須要用proto buffer語法定義好服務和消息後,運用不一樣的命令就能夠生成基於不一樣語言的服務和消息對應的代碼,而他們之間的通訊就像調用本身的方法同樣方便,底層則所有交給了grpc去處理。