GRPC
使用protocol buffer
进行接口定义和底层信息交换。客户端可以直接调用服务端的方法,就像调用本地方法一样。在服务端实现这个接口并且运行一个grpc
服务来处理客户端的IO调用。客户端有一个存根(stub),它提供与服务端相同的方法。
官网grpc介绍:https://grpc.io/docs/what-is-grpc/introduction/
先决条件:
json
$ apt-get update
$ apt-get install -y protobuf-compiler
$ protoc --version # Ensure compiler version is 3+
安装教程:https://grpc.io/docs/protoc-installation/
$ go install google.golang.org/protobuf/cmd/[email protected]
$ go install google.golang.org/grpc/cmd/[email protected]
$ export PATH="$PATH:$(go env GOPATH)/bin"
安装教程:https://grpc.io/docs/languages/go/quickstart/
go install 是安装编译。
go get是远端拉取,并编译。
如果用go get拉取并没有在GOPATH/bin目录下编译出protoc-gen-go.exe/protoc-gen-go-grpc.exe。
那么需要重新go install编译一次
syntax="proto3";
// package proto文件的包名(与go无关) 生成的语言类放在什么包里面 这个package必须紧跟着syntax="proto3"
package pbfiles.user;
import "google/api/annotations.proto";
// 定义生成的pb文件go的package名
option go_package = "/proto";
// 定义service服务 会翻译为UserServiceClient 和 userServiceClient 和对外暴露的 NewUserServiceClient供客户端调用
// 对外暴露GetUserList 和 GetUser方法
// 定义服务
service UserSerivce{
//定义获取用户列表信息方法
rpc GetUserList(UserRequest) returns (UserListReply) {}
// 定义获取用户信息方法
rpc GetUser(UserRequest) returns (UserReply) {
option (google.api.http) = {
get: "/helloworld/{name}"
};
}
// http
}
// 入参
message UserRequest{
string class_id= 1;
int32 phone= 2;
}
// GetUserList出参
message UserListReply{
repeated User user = 1;
}
// GetUser出参
message UserReply{
User user = 1;
}
message User {
string name = 1;
string phone = 2;
string class_id = 3;
string sex = 4;
int32 id = 5;
}
代码地址:https://github.com/qiushenglei/grpc-server-golang
│ go.mod
│ go.sum
│ main.go
│
├─.idea
│ .gitignore
│ etcd_new.iml
│ modules.xml
│ workspace.xml
│
├─models
│ user.go
│
├─proto
│ user.pb.go
│ user.proto
│ user_grpc.pb.go
├─third_party
| google
│
└─service
userService.go
$ protoc -I ./third_party \
--proto_path=proto--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
user.proto
生成文件:
.pb.go
:包含用于序列化
和检索请求
和响应消息
类型的所有协议缓冲区代码
grpc.pb.go
:包含
客户端使用服务定义的方法调用的接口类型(stub存根)
服务端要实现的接口
实例教程:https://grpc.io/docs/languages/go/basics/
protocol buffer
定义的接口tcp
监听端口// sevice/userSerivce.go
package service
import (
"context"
"etcd_new/models"
"etcd_new/proto"
"fmt"
"github.com/go-xorm/xorm"
"xorm.io/core"
)
// 实现.proto的service定义的接口
type UserService struct {
// 业务侧是查询sql,所以提前注册到rpc服务内
Engine *xorm.Engine
// grpc规范必须要引入,否则我无法实现这个接口,因为她有个must小写方法
proto.UnimplementedUserSerivceServer
}
// 数据库操作引擎
func NewMysqlEngine() *xorm.Engine {
engine, err := xorm.NewEngine("mysql", "user:password@tcp(ip:port)/elmcms?charset=utf8")
if err != nil {
panic(err)
}
engine.ShowSQL(true)
engine.Logger().SetLevel(core.LOG_DEBUG)
engine.SetMaxOpenConns(10)
//返回引擎
return engine
}
// 实现proto文件定义的rpc暴露的方法
func (s *UserService) GetUserList(ctx context.Context, request *proto.UserRequest) (*proto.UserListReply, error) {
var userList []models.User
err := s.Engine.Where("id > ?", 0).Find(&userList)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(userList)
fmt.Println("===========================================")
//因为返回的是[]*proto.User
res := make([]*proto.User, len(userList))
//遍历数据库查询结果 然后重新塞入到新的切片当中去
for _, u := range userList {
res = append(res, &proto.User{
Name: u.Name,
Phone: u.Phone,
Sex: u.Sex,
Id: u.ID,
})
}
return &proto.UserListReply{User: res}, err
}
func (s *UserService) GetUser(ctx context.Context, request *proto.UserRequest) (*proto.UserReply, error) {
// 业务code
return &proto.UserReply{}, nil
}
调用到的其他文件
// models/user.go
package models
type User struct {
Name string `xorm:"varchar(255)" json:"name"`
Phone string `xorm:"varchar(255)" json:"phone"`
Sex string `xorm:"varchar(255)" json:"sex"`
ID int32 `xorm:"int" json:"id"`
}
//main.go
package main
import (
"etcd_new/proto"
"etcd_new/service"
"flag"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
"net"
)
var tls = flag.Int("t", 0, "need tls")
var certFile = flag.String("cert", "", "cert file path")
var keyFile = flag.String("key", "", "key file path")
func main () {
flag.Parse()
//1.监听
lis, _ := net.Listen("tcp", "ip:port")
// 证书验证,没有的话可以省略
var opts []grpc.ServerOption
if *tls == 1 {
if *certFile == "" {
*certFile = "x509/server_cert.pem"
}
if *keyFile == "" {
*keyFile = "x509/server_key.pem"
}
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
opts = []grpc.ServerOption{grpc.Creds(creds)}
}
// 起GRPC服务
// 2.生成 grpc server结构体
grpcServer := grpc.NewServer(opts...)
// 3.生成Instance结构体
ins := &service.UserService{
Engine: service.NewMysqlEngine(),
}
// 给rpc服务注册,这个方法是proto-go生成的
proto.RegisterUserSerivceServer(grpcServer, ins)
// 开启服务, 这个地方我们不需要for死循环 grpc当中会自动帮助我们实时监听
grpcServer.Serve(lis)
}
代码地址: https://github.com/qiushenglei/grpc-client-go
│ go.mod
│ go.sum
│ main.go
│
├─.idea
│ .gitignore
│ modules.xml
│ test.iml
│ workspace.xml
│
├─proto
│ user.pb.go
│ user.proto
│ user_grpc.pb.go
package main
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"testproj/proto"
)
func main() {
//grpc链接客户端
conn, err := grpc.Dial("127.0.0.1:8899", grpc.WithInsecure())
if err != nil {
fmt.Println(err)
return
}
//程序完成不要忘记关闭链接
defer conn.Close()
//真正去链接服务端,pb-grpc生成的
client := proto.NewUserSerivceClient(conn)
//调用服务端 第一个是上线文 第二个是要传入的参数 传入的是结构体 返回的也是结构体
ret, err := client.GetUser(context.Background(), &proto.UserRequest{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ret.User.Id)
fmt.Println(ret.User.Name)
}