环境配置
1.# 下载最新版本23.2的protoc,这个是protobuf代码生成工具,通过proto文件生成对应的代码,根据自己操作系统下载相应文件,这里以windows 64位系统为例
wget https://github.com/protocolbuffers/protobuf/releases/download/v23.2/protoc-23.2-win64.zip
# 解压并放在windows本地目录,并配置在Path路径下如D:\Program Files\protoc-23.2-win64\bin,在windows下命令行执行protoc --version检查是否安装配置正确
2.# 创建go项目grpc-demo,在GoLand IDE编写,并通过下面命令安装grpc核心库protoc,可以GoLand IDE安装protoc插件,实现语法高亮
go get google.golang.org/grpc
3.# 在实际开发中最好指定具体的版本,这里是演示使用就直接用latest,在命令行中执行下面两条命令
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
文件结构
1.创建user-service模块
service/users.proto内容:
syntax = "proto3";
option go_package = "../service";
// 服务和方法
service Users {
rpc GetUser (UserGetRequest) returns (UserGetReply) {}
}
// 请求消息
message UserGetRequest {
string email = 1;
int32 id = 2;
}
// 响应消息
message User {
string id = 1;
string first_name = 2;
string last_name = 3;
int32 age = 4;
}
message UserGetReply {
User user = 1;
}
2.生成客户端和服务端代码
在service目录下执行以下命令,自动生成pb.go文件
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative users.proto
3.编写服务器
server/server.go内容:
package main
import (
"context"
"log"
"net"
"os"
users "testgo/service" // 导入之前生成的包
"google.golang.org/grpc"
)
// userService类型是Users服务的服务处理程序
type userService struct {
users.UnimplementedUsersServer // 这个字段对于gRPC中的任何服务实现都是强制性的
}
func (s *userService) GetUser(ctx context.Context, in *users.UserGetRequest) (*users.UserGetReply, error) {
// 打印客户端传过来的数据
log.Printf("已接收到邮件地址: %s, 还有ID: %d", in.Email, in.Id)
// 自定义数据响应给客户端
u := users.User{
Id: "user-782911",
FirstName: "mike",
LastName: "li",
Age: 22,
}
return &users.UserGetReply{User: &u}, nil
}
// 向gRPC服务器注册Users服务
func registerServices(s *grpc.Server) {
users.RegisterUsersServer(s, &userService{})
}
// 启动gRPC服务器
func startServer(s *grpc.Server, l net.Listener) error {
return s.Serve(l)
}
func main() {
listenAddr := os.Getenv("LISTEN_ADDR")
if len(listenAddr) == 0 {
listenAddr = ":50051"
}
lis, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
registerServices(s)
log.Fatal(startServer(s, lis))
}
- 在gRPC服务实现中,都需要嵌入一个users.UnimplementedUsersServer字段(在grpc.pb.go中存在该字段),将该字段嵌入到userService的struct中。
- 实现proto中定义的GetUser接口,接收器为userService的指针类型,在方法内可以直接访问结构体的字段并修改。GetUser接口入参为context和proto定义的request指针,返回值是proto定义的response指针以及error。注意返回的response要按照proto定义的response格式实现。
- 新建注册user服务方法,调用grpc.pb.go的RegisterUsersServer方法,该方法需要两个参数:一个grpc.Server类型的指针(grpc的服务对象)以及一个实现了UsersServer接口的对象(在这里userService结构体实现了UsersServer接口,它以指针方式传给RegisterUsersServer),RegisterUsersServer会将这个对象注册到gRPC服务中,这样客户端就可以通过这个对象调用定义在接口中的方法了。
- 新建启动gRPC服务方法,其中入参有两个:s是已经初始化好的grpc.Server对象,l是通过net.Listen获取的监听套接字。Serve方法将grpc服务绑定到监听套接字l上,开始接收连接(阻塞)。
- main的步骤十分清晰,先获取监听套接字,新建gRPC服务对象,注册服务,启动服务监听请求。
4.编写客户端
package main
import (
"context"
"log"
"os"
users "testgo/service" // 导入之前生成的包
"google.golang.org/grpc"
)
// 建立与服务器的连接(通道)
func setupGrpcConnection(addr string) (*grpc.ClientConn, error) {
return grpc.DialContext(
context.Background(),
addr,
grpc.WithInsecure(),
grpc.WithBlock(),
)
}
// 创建客户端与Users服务通信
func getUserServiceClient(conn *grpc.ClientConn) users.UsersClient {
return users.NewUsersClient(conn)
}
// 调用Users服务中的GetUser()方法
func getUser(client users.UsersClient, u *users.UserGetRequest) (*users.UserGetReply, error) {
return client.GetUser(context.Background(), u)
}
func main() {
if len(os.Args) != 2 {
log.Fatal("缺少gRPC服务器地址")
}
conn, err := setupGrpcConnection(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer conn.Close()
c := getUserServiceClient(conn)
result, err := getUser(c, &users.UserGetRequest{
Email: "mike_li@163.com",
Id: 8082731,
})
if err != nil {
log.Fatal(err)
}
// 打印响应
log.Printf("收到响应: %s %s %s %d\n", result.User.Id, result.User.FirstName, result.User.LastName, result.User.Age)
}
- 新建 建立与服务器连接(通道)的方法,这里的入参就是监听的端口号,返回一个*grpc.ClientConn对象。后续Client可以使用这个ClientConn对象来发送RPC请求。这里内部使用grpc.DialContext来创建连接,grpc.WithInsecure() 表示使用不安全连接,即不验证 TLS 证书,grpc.WithBlock() 设置连接是阻塞的。该方法的作用是封装了gRPC客户端连接的创建,提供了一个简单的接口给调用者使用。
- 在getUserServiceClient方法中,使用返回的ClientConn对象来创建具体的服务客户端,NewUsersClient方法是pb.go文件自动生成的。
- 在getUser方法中,使用创建好的具体的服务客户端和request调用Users服务中的GetUser()方法。注意此处的GetUser()是服务端实现的remote方法,该方法的定义在grpc.pb.go中。
- main中步骤较清晰,先获取一个Client连接对象(注意用defer在最后return后关闭该连接),使用该连接对象创建具体的客户端,调用本地getUser()方法(实际调用remote服务端的GetUser()方法),传入client对象以及request信息,获取响应。
5.效果验证
# 1.服务端执行(在server文件夹下):
go run .\server.go
# 2.客户端执行(开启另外一个终端,在client文件夹下):
go run main.go localhost:50051